Collections.sort与多个字段

我有一个“报告”对象的列表与三个字段(所有stringtypes) –

ReportKey StudentNumber School 

我有一个sorting代码像 –

 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()); } }); 

出于某种原因,我没有sorting的顺序。 有人build议在田间放置空间,但为什么?

你看到代码有什么问题吗?

你看到代码有什么问题吗?

是。 为什么在比较之前将三个字段加在一起?

我可能会做这样的事情:(假设字段是你想要sorting他们的顺序)

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

我会使用Guava的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(); } } 

(从基于多个字段的对Java中的对象列表进行sorting )

工作代码在这个要点

杂乱而复杂:手工sorting

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

这需要大量的打字,维护并且容易出错。

反思的方式:用BeanComparator进行sorting

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

显然,这更简洁,但更容易出错,因为通过使用string(没有types安全性,自动重构)而直接引用字段。 现在,如果一个字段被重命名,编译器甚至不会报告问题。 而且,因为这个解决scheme使用了reflection,sorting要慢得多。

到达目的地:使用Google Guava的ComparisonChain进行sorting

 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(); */ } }); 

这是好得多的,但是对于最常见的用例需要一些锅炉板代码:默认情况下,空值应该被低估。 对于空字段,你必须向Guava提供一个额外的指令,在这种情况下做什么。 这是一个灵活的机制,如果你想做一些特定的事情,但往往你想要的默认情况下(即1,A,B,Z,空)。

使用Apache Commons CompareToBuilder进行sorting

 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(); } }); 

像Guava的ComparisonChain一样,这个库类可以很容易地在多个字段上进行sorting,但是也可以定义空值(即1,a,b,z,null)的默认行为。 但是,除非您提供自己的比较器,否则您不能指定其他任何内容。

从而

最终,它归结为味道和灵活性(Guava的ComparisonChain)与简洁的代码(Apache的CompareToBuilder)的需要。

奖金方法

我发现了一个很好的解决scheme,它将多个比较器按照MultiComparator CodeReview的优先级顺序组合在一起:

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

当然,Apache Commons Collections已经有了一个util:

ComparatorUtils.chainedComparator(comparatorCollection)

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

如果你想按报告键,然后学生号码,然后学校,你应该做这样的事情:

 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()); } } 

当然,如果您需要为报告,报告密钥,学生人数或学校允许为空值,则这些值都不能为空。

虽然你可以使用空格来使string连接版本工作,但是在奇怪的情况下,如果你有奇数的数据本身包含空格等,它仍然会失败。上面的代码是你想要的逻辑代码…先按报告键比较,然后如果报告键是相同的,那么只会打扰学生的号码。

这是一个古老的问题,所以我没有看到一个Java 8的等价物。 这是一个这个具体情况的例子。

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

如果您想先根据ReportKey进行sorting,然后再selectStudent Number然后School,则需要比较每个string,而不是连接它们。 如果使用空格填充string以使每个ReportKey具有相同的长度等等,则您的方法可能会有效,但这并不值得。 相反,只要更改比较方法来比较ReportKeys,如果compareTo返回0,然后尝试StudentNumber,然后学校。

如果StudentNumber是数字,它将不会被sorting数字,但字母数字。 不要指望

 "2" < "11" 

这将是:

 "11" < "2" 

在Java8中用多个字段进行sorting

 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 + "]"; } }