Collections.sort con più campi

Ho una lista di oggetti “Report” con tre campi (tipo All String) –

ReportKey StudentNumber School 

Ho un codice di ordinamento come-

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

Per qualche motivo, non ho l’ordine ordinato. Si consiglia di inserire spazi tra i campi, ma perché?

Vedi qualcosa di sbagliato nel codice?

Vedi qualcosa di sbagliato nel codice?

Sì. Perché stai aggiungendo i tre campi insieme prima di confrontarli?

Probabilmente farei qualcosa del genere: (presumendo che i campi siano nell’ordine in cui desideri ordinarli)

 @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; } 

Farei un comparatore usando il ComparisonChain Guava :

 public class ReportComparator implements Comparator { 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(); } } 

(da Modi per ordinare elenchi di oggetti in Java basati su più campi )

Codice di lavoro in questo senso

Disordinato e contorto: ordinamento manuale

 Collections.sort(pizzas, new Comparator() { @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); } }); 

Ciò richiede molta digitazione, manutenzione ed è sobject a errori.

Il modo riflessivo: ordinare con BeanComparator

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

Ovviamente questo è più conciso, ma ancora più sobject a errori in quanto si perde il riferimento diretto ai campi usando invece le stringhe (no typesafety, auto-refactoring). Ora se un campo viene rinominato, il compilatore non segnalerà nemmeno un problema. Inoltre, poiché questa soluzione utilizza la riflessione, l’ordinamento è molto più lento.

Come arrivare: ordinamento con ComparisonChain di Google Guava

 Collections.sort(pizzas, new Comparator() { @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(); */ } }); 

Questo è molto meglio, ma richiede un codice piastra caldaia per il caso d’uso più comune: i valori nulli dovrebbero essere valutati meno di default. Per i campi null, devi fornire una direttiva extra a Guava su cosa fare in quel caso. Questo è un meccanismo flessibile se vuoi fare qualcosa di specifico, ma spesso vuoi il caso predefinito (ad esempio 1, a, b, z, null).

Ordinamento con Apache Commons CompareToBuilder

 Collections.sort(pizzas, new Comparator() { @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(); } }); 

Come il Confronto di Guava, questa class di libreria ordina facilmente su più campi, ma definisce anche il comportamento predefinito per valori nulli (ad esempio 1, a, b, z, null). Tuttavia, non è ansible specificare nient’altro, a meno che non si fornisca il proprio Comparatore.

così

In definitiva si tratta di sapore e necessità di flessibilità (Guava’s ComparisonChain) rispetto al codice conciso (Apache’s CompareToBuilder).

Metodo bonus

Ho trovato una soluzione che combina più comparatori in ordine di priorità su CodeReview in un MultiComparator :

 class MultiComparator implements Comparator { private final List> comparators; public MultiComparator(List> comparators) { this.comparators = comparators; } public MultiComparator(Comparator... comparators) { this(Arrays.asList(comparators)); } public int compare(T o1, T o2) { for (Comparator c : comparators) { int result = c.compare(o1, o2); if (result != 0) { return result; } } return 0; } public static  void sort(List list, Comparator... comparators) { Collections.sort(list, new MultiComparator(comparators)); } } 

Ofcourse Apache Commons Collections ha già un utile per questo:

ComparatorUtils.chainedComparator (comparatorCollection)

 Collections.sort(list, ComparatorUtils.chainedComparator(comparators)); 

Se vuoi ordinare per chiave di rapporto, quindi numero di studenti, poi scuola, dovresti fare qualcosa del genere:

 public class ReportComparator implements Comparator { 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()); } } 

Ciò presuppone che nessuno dei valori possa essere nullo, ovviamente – diventa più complicato se è necessario consentire valori nulli per il report, la chiave del report, il numero dello studente o la scuola.

Mentre si poteva ottenere la versione della concatenazione delle stringhe per lavorare usando gli spazi, fallirebbe comunque in strani casi se si dispongano di dati strani che includessero spazi ecc. Il codice precedente è il codice logico che si desidera … confrontare prima per chiave di segnalazione, poi preoccupati solo del numero dello studente se le chiavi del rapporto sono uguali, ecc.

Questa è una vecchia domanda quindi non vedo un equivalente Java 8. Ecco un esempio per questo caso specifico.

 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 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 + '\'' + '}'; } } } 

Ordinamento con più campi 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 empList = getEmpList(); // Before Java 8 empList.sort(new Comparator() { @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 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 + "]"; } } 

Se si desidera ordinare in base a ReportKey prima quindi Numero studente poi Scuola, è necessario confrontare ogni stringa invece di concatenarli. Il tuo metodo potrebbe funzionare se si riempiono le stringhe con spazi in modo che ogni ReportKey abbia la stessa lunghezza e così via, ma non ne vale la pena. Invece basta cambiare il metodo di confronto per confrontare i ReportKeys, se compareTorna 0, quindi prova StudentNumber, poi School.

Se lo StudentNumber è numerico, non sarà ordinato numerico ma alfanumerico. Non aspettare

 "2" < "11" 

sarà:

 "11" < "2" 

Ecco un esempio completo che confronta 2 campi in un object, uno String e uno int, utilizzando anche Collator per ordinare.

 public class Test { public static void main(String[] args) { Collator myCollator; myCollator = Collator.getInstance(Locale.US); List items = new ArrayList(); 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() { @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; } } } 

Produzione:

costrels 1039737

costa 310831

costa 310832

Costi 1570019

Suggerisco di utilizzare l’approccio Java 8 Lambda:

 List reportList = new ArrayList(); reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));