Collections.sort mit mehreren Feldern

83

Ich habe eine Liste von "Berichts" -Objekten mit drei Feldern (Typ "Alle Zeichenfolgen").

ReportKey
StudentNumber
School

Ich habe einen Sortiercode wie folgt:

Collections.sort(reportList, new Comparator<Report>() {

@Override
public int compare(final Report record1, final Report record2) {
      return (record1.getReportKey() + record1.getStudentNumber() + record1.getSchool())                      
        .compareTo(record2.getReportKey() + record2.getStudentNumber() + record2.getSchool());
      }

});

Aus irgendeinem Grund habe ich die sortierte Reihenfolge nicht. Man riet, Leerzeichen zwischen Felder zu setzen, aber warum?

Sehen Sie etwas falsch mit dem Code?

Milli Szabo
quelle
Sind sie Felder fester Länge? Was passiert, wenn record1.getReportKey () "AB" und record1.getStudentNumber () "CD" ist, record2.getReportKey () jedoch "ABCD"?
Mellamokb
Feste Länge. Entschuldigung vergessen zu erwähnen.
Milli Szabo

Antworten:

136

Sehen Sie etwas falsch mit dem Code?

Ja. Warum addieren Sie die drei Felder, bevor Sie sie vergleichen?

Ich würde wahrscheinlich so etwas tun: (vorausgesetzt, die Felder sind in der Reihenfolge, in der Sie sie sortieren möchten)

@Override public int compare(final Report record1, final Report record2) {
    int c;
    c = record1.getReportKey().compareTo(record2.getReportKey());
    if (c == 0)
       c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
    if (c == 0)
       c = record1.getSchool().compareTo(record2.getSchool());
    return c;
}
Jason S.
quelle
Bitte erläutern Sie. Wie mache ich das dann? Vielen Dank.
Milli Szabo
Hallo, können Sie mehr hinzufügen, wenn (c == 0)? Ich weiß nicht, ob es richtig ist, aber es scheint, dass nein, denn wenn die erste Bedingung erfüllt ist, tritt nie die zweite oder dritte ein .. etc ..
10
Ich glaube nicht, dass du es verstanden hast a.compareTo(b). Die Konvention ist, dass ein Wert von 0 Gleichheit darstellt, negative Ganzzahlen dies darstellen a < bund positive Ganzzahlen dies a > bfür Comparable aund darstellen b.
Jason S
1
Es funktioniert nicht. Ich habe das versucht und es funktioniert nicht. Es funktioniert mit nur einemcompareTo
Shimatai
110

(ursprünglich von Möglichkeiten zum Sortieren von Objektlisten in Java anhand mehrerer Felder )

Originaler Arbeitscode in diesem Kern

Verwenden von Java 8 Lambda (hinzugefügt am 10. April 2019)

Java 8 löst dies gut durch Lambda (obwohl Guava und Apache Commons möglicherweise noch mehr Flexibilität bieten):

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

Vielen Dank an @ gaoagongs Antwort unten .

Beachten Sie, dass ein Vorteil hierbei darin besteht, dass die Getter träge bewertet werden (z. B. getSchool()wird nur bewertet, wenn dies relevant ist).

Chaotisch und verschlungen: Sortieren von Hand

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        int sizeCmp = p1.size.compareTo(p2.size);  
        if (sizeCmp != 0) {  
            return sizeCmp;  
        }  
        int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
        if (nrOfToppingsCmp != 0) {  
            return nrOfToppingsCmp;  
        }  
        return p1.name.compareTo(p2.name);  
    }  
});  

Dies erfordert viel Eingabe, Wartung und ist fehleranfällig. Der einzige Vorteil ist, dass Getter nur dann aufgerufen werden, wenn dies relevant ist.

Der reflektierende Weg: Sortieren mit BeanComparator

ComparatorChain chain = new ComparatorChain(Arrays.asList(
   new BeanComparator("size"), 
   new BeanComparator("nrOfToppings"), 
   new BeanComparator("name")));

Collections.sort(pizzas, chain);  

Dies ist natürlich prägnanter, aber noch fehleranfälliger, da Sie Ihren direkten Verweis auf die Felder verlieren, indem Sie stattdessen Strings verwenden (keine Typensicherheit, automatische Refactorings). Wenn nun ein Feld umbenannt wird, meldet der Compiler nicht einmal ein Problem. Da diese Lösung Reflexion verwendet, ist die Sortierung außerdem viel langsamer.

Anreise: Sortieren mit Google Guavas ComparisonChain

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
        // or in case the fields can be null:  
        /* 
        return ComparisonChain.start() 
           .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
           .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
           .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
           .result(); 
        */  
    }  
});  

Dies ist viel besser, erfordert jedoch einen Kesselplattencode für den häufigsten Anwendungsfall: Nullwerte sollten standardmäßig weniger bewertet werden. Für Nullfelder müssen Sie Guava eine zusätzliche Anweisung geben, was in diesem Fall zu tun ist. Dies ist ein flexibler Mechanismus, wenn Sie etwas Bestimmtes tun möchten, aber häufig den Standardfall (z. B. 1, a, b, z, null).

Und wie in den Kommentaren unten angegeben, werden diese Getter alle sofort für jeden Vergleich bewertet.

Sortieren mit Apache Commons CompareToBuilder

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
    }  
});  

Wie Guavas ComparisonChain sortiert diese Bibliotheksklasse problemlos nach mehreren Feldern, definiert jedoch auch das Standardverhalten für Nullwerte (z. B. 1, a, b, z, null). Sie können jedoch auch nichts anderes angeben, es sei denn, Sie stellen Ihren eigenen Komparator bereit.

Wie in den Kommentaren unten angegeben, werden diese Getter alle sofort für jeden Vergleich bewertet.

So

Letztendlich kommt es auf den Geschmack und das Bedürfnis nach Flexibilität (Guavas ComparisonChain) im Vergleich zu prägnantem Code (Apaches CompareToBuilder) an.

Bonusmethode

Ich fand eine schöne Lösung , dass mehrere Komparatoren in der Reihenfolge ihrer Priorität Mähdrescher auf Codereview in einem MultiComparator:

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

Natürlich hat Apache Commons Collections bereits einen Nutzen dafür:

ComparatorUtils.chainedComparator (compareatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
Benny Bottema
quelle
Perfekte Lösung für sauberen Code und
erfüllt auch
großartig mit Java 8 Lambda
Frank
Danke für die tolle Antwort, Benny. Ich habe ein Szenario, in dem sich die Eigenschaft nicht direkt in meinem Objekt befindet. Es gibt jedoch ein verschachteltes Objekt. Wie mache ich das in diesem Fall? wie zum Beispiel hier Collections.sort (reportList, Comparator.comparing (Report :: getReportKey) .thenComparing (Report :: getStudentNumber) .thenComparing (Report :: getSchool)); Innerhalb des Berichtsobjekts habe ich ein Schülerobjekt und innerhalb des Schülerobjekts habe ich eine Schülernummer. Wie sortieren wir das hier in diesem Fall? Jede Hilfe wäre dankbar.
Madhu Reddy
@MadhuReddy Im Beispiel werden Methodenreferenzen als Lambdas verwendet, aber Sie können einfach ein geeignetes Lambda angeben, das stattdessen das entsprechende verschachtelte Feld zurückgibt.
Benny Bottema
44

Ich würde einen Komparator mit machen Guava ‚s ComparisonChain:

public class ReportComparator implements Comparator<Report> {
  public int compare(Report r1, Report r2) {
    return ComparisonChain.start()
        .compare(r1.getReportKey(), r2.getReportKey())
        .compare(r1.getStudentNumber(), r2.getStudentNumber())
        .compare(r1.getSchool(), r2.getSchool())
        .result();
  }
}
ColinD
quelle
20

Dies ist eine alte Frage, daher sehe ich kein Java 8-Äquivalent. Hier ist ein Beispiel für diesen speziellen Fall.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Compares multiple parts of the Report object.
 */
public class SimpleJava8ComparatorClass {

    public static void main(String[] args) {
        List<Report> reportList = new ArrayList<>();
        reportList.add(new Report("reportKey2", "studentNumber2", "school1"));
        reportList.add(new Report("reportKey4", "studentNumber4", "school6"));
        reportList.add(new Report("reportKey1", "studentNumber1", "school1"));
        reportList.add(new Report("reportKey3", "studentNumber2", "school4"));
        reportList.add(new Report("reportKey2", "studentNumber2", "school3"));

        System.out.println("pre-sorting");
        System.out.println(reportList);
        System.out.println();

        Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

        System.out.println("post-sorting");
        System.out.println(reportList);
    }

    private static class Report {

        private String reportKey;
        private String studentNumber;
        private String school;

        public Report(String reportKey, String studentNumber, String school) {
            this.reportKey = reportKey;
            this.studentNumber = studentNumber;
            this.school = school;
        }

        public String getReportKey() {
            return reportKey;
        }

        public void setReportKey(String reportKey) {
            this.reportKey = reportKey;
        }

        public String getStudentNumber() {
            return studentNumber;
        }

        public void setStudentNumber(String studentNumber) {
            this.studentNumber = studentNumber;
        }

        public String getSchool() {
            return school;
        }

        public void setSchool(String school) {
            this.school = school;
        }

        @Override
        public String toString() {
            return "Report{" +
                   "reportKey='" + reportKey + '\'' +
                   ", studentNumber='" + studentNumber + '\'' +
                   ", school='" + school + '\'' +
                   '}';
        }
    }
}
Gaoagong
quelle
comparingund thenComparingfür den Sieg!
Asgs
1
Es fragt nach mindestens Android-Version 24.
Viper
14

Wenn Sie nach Berichtsschlüssel, Schülernummer und Schule sortieren möchten, sollten Sie Folgendes tun:

public class ReportComparator implements Comparator<Report>
{
    public int compare(Report r1, Report r2)
    {
        int result = r1.getReportKey().compareTo(r2.getReportKey());
        if (result != 0)
        {
            return result;
        }
        result = r1.getStudentNumber().compareTo(r2.getStudentNumber());
        if (result != 0)
        {
            return result;
        }
        return r1.getSchool().compareTo(r2.getSchool());
    }
}

Dies setzt natürlich voraus, dass keiner der Werte null sein kann - es wird komplizierter, wenn Sie Nullwerte für den Bericht, den Berichtsschlüssel, die Schülernummer oder die Schule zulassen müssen.

Während Sie die Version zur Verkettung von Zeichenfolgen mithilfe von Leerzeichen zum Laufen bringen könnten , würde dies in seltsamen Fällen immer noch fehlschlagen, wenn Sie ungerade Daten hätten, die selbst Leerzeichen usw. enthalten. Der obige Code ist der gewünschte logische Code ... Vergleichen Sie ihn zuerst mit dem Berichtsschlüssel sich nur mit der Schülernummer beschäftigen, wenn die Berichtsschlüssel identisch sind usw.

Jon Skeet
quelle
6
Obwohl es mit diesem Code nicht "falsch" ist und ich es verstehe. Ich bevorzuge Jasons Implementierung, weil es einfacher zu folgen scheint, da er nur eine return-Anweisung hat.
JZD
Wenn ich versuche, Ihren Code zu verwenden, kann er keine compareTo()Methode verwenden. Könnten Sie mir bitte helfen, das Problem zu lösen?
Viper
@viper: Nun nein, weil ich nicht implementiere Comparable<T>, implementiere ich Comparator<T>. Wir wissen nicht, was Sie erreichen wollen, was Sie versucht haben oder was schief gelaufen ist. Vielleicht sollten Sie eine neue Frage stellen, wahrscheinlich nachdem Sie mehr recherchiert haben. (Möglicherweise wurde es bereits gefragt.)
Jon Skeet
7

Ich schlage vor, den Java 8 Lambda-Ansatz zu verwenden:

List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));
Zia Ul Mustafa
quelle
5

Sortieren mit mehreren Feldern in Java8

package com.java8.chapter1;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static java.util.Comparator.*;



 public class Example1 {

    public static void main(String[] args) {
        List<Employee> empList = getEmpList();


        // Before Java 8 
        empList.sort(new Comparator<Employee>() {

            @Override
            public int compare(Employee o1, Employee o2) {
                int res = o1.getDesignation().compareTo(o2.getDesignation());
                if (res == 0) {
                    return o1.getSalary() > o2.getSalary() ? 1 : o1.getSalary() < o2.getSalary() ? -1 : 0;
                } else {
                    return res;
                }

            }
        });
        for (Employee emp : empList) {
            System.out.println(emp);
        }
        System.out.println("---------------------------------------------------------------------------");

        // In Java 8

        empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));
        empList.stream().forEach(System.out::println);

    }
    private static List<Employee> getEmpList() {
        return Arrays.asList(new Employee("Lakshman A", "Consultent", 450000),
                new Employee("Chaitra S", "Developer", 250000), new Employee("Manoj PVN", "Developer", 250000),
                new Employee("Ramesh R", "Developer", 280000), new Employee("Suresh S", "Developer", 270000),
                new Employee("Jaishree", "Opearations HR", 350000));
    }
}

class Employee {
    private String fullName;
    private String designation;
    private double salary;

    public Employee(String fullName, String designation, double salary) {
        super();
        this.fullName = fullName;
        this.designation = designation;
        this.salary = salary;
    }

    public String getFullName() {
        return fullName;
    }

    public String getDesignation() {
        return designation;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee [fullName=" + fullName + ", designation=" + designation + ", salary=" + salary + "]";
    }

}
Lakshman Miani
quelle
empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));Dieser Code hat mir geholfen. Vielen Dank
Sumit Badaya
Es fragt, mindestens Android-Version 24 zu verwenden.
Viper
4

Wenn die StudentNumber numerisch ist, wird sie nicht numerisch, sondern alphanumerisch sortiert. Erwarte nicht

"2" < "11"

es wird sein:

"11" < "2"
FrVaBe
quelle
Dies beantwortet die eigentliche Frage, warum das Ergebnis falsch ist.
Florian F
3

Wenn Sie zuerst nach ReportKey, dann nach Schülernummer und dann nach Schule sortieren möchten, müssen Sie jeden String vergleichen, anstatt ihn zu verketten. Ihre Methode funktioniert möglicherweise, wenn Sie die Zeichenfolgen mit Leerzeichen auffüllen, sodass jeder ReportKey dieselbe Länge usw. hat, aber die Mühe lohnt sich nicht wirklich. Ändern Sie stattdessen einfach die Vergleichsmethode, um die ReportKeys zu vergleichen. Wenn compareTo 0 zurückgibt, versuchen Sie es mit StudentNumber und dann mit School.

jzd
quelle
3

Verwenden Sie die ComparatorSchnittstelle mit den in JDK1.8 eingeführten Methoden: comparingund thenComparingoder konkreteren Methoden: comparingXXXund thenComparingXXX.

Wenn wir zum Beispiel zuerst eine Liste von Personen nach ihrer ID sortieren möchten, dann Alter, dann Name:

            Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
                    .thenComparingInt(Person::getAge)
                    .thenComparing(Person::getName);
            personList.sort(comparator);
Puppylpg
quelle
0

Hier ist ein vollständiges Beispiel für den Vergleich von zwei Feldern in einem Objekt, einem String und einem int, wobei auch Collator zum Sortieren verwendet wird.

public class Test {

    public static void main(String[] args) {

        Collator myCollator;
        myCollator = Collator.getInstance(Locale.US);

        List<Item> items = new ArrayList<Item>();

        items.add(new Item("costrels", 1039737, ""));
        items.add(new Item("Costs", 1570019, ""));
        items.add(new Item("costs", 310831, ""));
        items.add(new Item("costs", 310832, ""));

        Collections.sort(items, new Comparator<Item>() {
            @Override
            public int compare(final Item record1, final Item record2) {
                int c;
                //c = record1.item1.compareTo(record2.item1); //optional comparison without Collator                
                c = myCollator.compare(record1.item1, record2.item1);
                if (c == 0) 
                {
                    return record1.item2 < record2.item2 ? -1
                            :  record1.item2 > record2.item2 ? 1
                            : 0;
                }
                return c;
            }
        });     

        for (Item item : items)
        {
            System.out.println(item.item1);
            System.out.println(item.item2);
        }       

    }

    public static class Item
    {
        public String item1;
        public int item2;
        public String item3;

        public Item(String item1, int item2, String item3)
        {
            this.item1 = item1;
            this.item2 = item2;
            this.item3 = item3;
        }       
    }

}

Ausgabe:

costrels 1039737

kostet 310831

kostet 310832

Kosten 1570019

Live-Liebe
quelle
0

Viele der oben genannten Antworten enthalten Felder, die mit der Einzelkomparatormethode verglichen werden, die tatsächlich nicht funktioniert. Es gibt einige Antworten, obwohl für jedes Feld unterschiedliche Komparatoren implementiert sind. Ich poste dies, da dieses Beispiel meiner Meinung nach viel klarer und einfacher zu verstehen wäre.

class Student{
    Integer bornYear;
    Integer bornMonth;
    Integer bornDay;
    public Student(int bornYear, int bornMonth, int bornDay) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;
        this.bornDay = bornDay;
    }
    public Student(int bornYear, int bornMonth) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;

    }
    public Student(int bornYear) {

        this.bornYear = bornYear;

    }
    public Integer getBornYear() {
        return bornYear;
    }
    public void setBornYear(int bornYear) {
        this.bornYear = bornYear;
    }
    public Integer getBornMonth() {
        return bornMonth;
    }
    public void setBornMonth(int bornMonth) {
        this.bornMonth = bornMonth;
    }
    public Integer getBornDay() {
        return bornDay;
    }
    public void setBornDay(int bornDay) {
        this.bornDay = bornDay;
    }
    @Override
    public String toString() {
        return "Student [bornYear=" + bornYear + ", bornMonth=" + bornMonth + ", bornDay=" + bornDay + "]";
    }


}
class TestClass
{       

    // Comparator problem in JAVA for sorting objects based on multiple fields 
    public static void main(String[] args)
    {
        int N,c;// Number of threads

        Student s1=new Student(2018,12);
        Student s2=new Student(2018,12);
        Student s3=new Student(2018,11);
        Student s4=new Student(2017,6);
        Student s5=new Student(2017,4);
        Student s6=new Student(2016,8);
        Student s7=new Student(2018);
        Student s8=new Student(2017,8);
        Student s9=new Student(2017,2);
        Student s10=new Student(2017,9);

        List<Student> studentList=new ArrayList<>();
        studentList.add(s1);
        studentList.add(s2);
        studentList.add(s3);
        studentList.add(s4);
        studentList.add(s5);
        studentList.add(s6);
        studentList.add(s7);
        studentList.add(s8);
        studentList.add(s9);
        studentList.add(s10);

        Comparator<Student> byMonth=new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                if(st1.getBornMonth()!=null && st2.getBornMonth()!=null) {
                    return st2.getBornMonth()-st1.getBornMonth();
                }
                else if(st1.getBornMonth()!=null) {
                    return 1;
                }
                else {
                    return -1;
                }
        }};

        Collections.sort(studentList, new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                return st2.getBornYear()-st1.getBornYear();
        }}.thenComparing(byMonth));

        System.out.println("The sorted students list in descending is"+Arrays.deepToString(studentList.toArray()));



    }

}

AUSGABE

Die sortierte Schülerliste in absteigender Reihenfolge lautet [Student [bornYear = 2018, bornMonth = null, bornDay = null], Student [bornYear = 2018, bornMonth = 12, bornDay = null], Student [bornYear = 2018, bornMonth = 12, bornDay = null], Student [bornYear = 2018, bornMonth = 11, bornDay = null], Student [bornYear = 2017, bornMonth = 9, bornDay = null], Student [bornYear = 2017, bornMonth = 8, bornDay = null], Student [ bornYear = 2017, bornMonth = 6, bornDay = null], Student [bornYear = 2017, bornMonth = 4, bornDay = null], Student [bornYear = 2017, bornMonth = 2, bornDay = null], Student [bornYear = 2016, bornMonth = 8, bornDay = null]]

Naseer Mohammad
quelle