具有多个字段的Collections.sort


83

我有一个包含三个字段(所有字符串类型)的“报告”对象列表-

ReportKey
StudentNumber
School

我有一个排序代码,就像-

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

});

由于某种原因,我没有排序顺序。一个建议在字段之间插入空格,但是为什么呢?

您认为代码有什么问题吗?


它们是定长字段吗?如果record1.getReportKey()是“ AB”,而record1.getStudentNumber()是“ CD”,而record2.getReportKey()是“ ABCD”,会发生什么呢?
mellamokb

固定长度。对不起忘了提。
Milli Szabo,2010年

Answers:


136

您认为代码有什么问题吗?

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

我可能会做这样的事情:(假设字段按照您希望对其进行排序的顺序)

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

请详细说明。那我该怎么做呢?谢谢。
Milli Szabo,2010年

嗨,如果(c == 0)?我不知道,如果是正确的,但似乎没有,因为如果第一个条件得到满足将不会进入第二或第三。等等。

10
我想你不懂a.compareTo(b); 的惯例是值0表示平等,负整数表示a < b和正整数表示a > bComparable ab
詹森·S

1
没用 我已经尝试过了,但是没有用。它仅适用于一个compareTo
shimatai

107

(最初来自基于多个字段对Java对象列表进行排序的方式

要点中的原始工作代码

使用Java 8 lambda(2019年4月10日添加)

Java 8通过lambda很好地解决了这个问题(尽管Guava和Apache Commons可能仍然提供更大的灵活性):

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

感谢@gaoagong在下面回答

请注意,这里的一个优点是可以对吸气剂进行延迟评估(例如getSchool(),仅在相关时评估)。

凌乱和混乱:手工排序

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

这需要大量的输入,维护并且容易出错。唯一的好处是,仅在相关时才调用getter

反射方式:使用BeanComparator排序

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

Collections.sort(pizzas, chain);  

显然,这更加简洁,但是当您通过使用字符串(没有类型安全性,自动重构)而失去对字段的直接引用时,更容易出错。现在,如果重命名了字段,则编译器甚至不会报告问题。而且,由于此解决方案使用反射,因此排序速度要慢得多。

到达那里:使用Google Guava的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(); 
        */  
    }  
});  

这样做要好得多,但是对于最常见的用例,需要一些样板代码:默认情况下,空值的值应较小。对于空字段,您必须提供一个额外的指令给Guava,在这种情况下该怎么做。如果您想做一些特定的事情,但是通常您想要默认情况(即1,a,b,z,null),这是一种灵活的机制。

而且,正如下面的评论所指出的,这些吸气剂都将在每次比较时立即进行评估。

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

像Guava的ComparisonChain一样,该库类可在多个字段上轻松排序,但还定义了null值(即1,a,b,z,null)的默认行为。但是,除非您提供自己的比较器,否则您也不能指定其他任何内容。

再次,如下面的评论中所述,对于每个比较,这些吸气剂都将立即进行评估。

从而

最终归结为风味和灵活性的需求(Guava的CompareChain)与简洁的代码(Apache的CompareToBuilder)。

奖金法

我找到了一个不错的解决方案,该解决方案在CodeReview中的优先级顺序中组合了多个比较器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));
    }
}

当然,Apache Commons Collections已经有了一个实用程序:

ComparatorUtils.chainedComparator(comparatorCollection)

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

完美代码的完美解决方案,也可以达到目的
cryptonkid

Java 8 lambda很棒
坦率的

感谢您的出色回答Benny。我有一个场景,其中属性不直接位于对象内。但是有一个嵌套的对象。在那种情况下我该怎么办?例如,在这里Collections.sort(reportList,Comparator.comparing(Report :: getReportKey).thenComparing(Report :: getStudentNumber).thenComparing(Report :: getSchool)); 在报表对象内部,我有一个学生对象,然后在学生对象内部,我有一个学生编号。在这种情况下,我们该如何分类呢?任何帮助,将不胜感激。
Madhu Reddy

@MadhuReddy在示例中,方法引用用作lambda的引用,但是您可以只提供适当的lambda来返回相应的嵌套字段。
Benny Bottema

44

我会想办法让使用比较番石榴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();
  }
}

20

这是一个老问题,所以我看不到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 + '\'' +
                   '}';
        }
    }
}

comparingthenComparing为赢!
asgs 2013年

1
它要求最低的android版本24。–
viper

14

如果要按报告键,学生编号和学校排序,则应执行以下操作:

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

当然,这假定所有值都不可以为null-如果您需要为报告,报告密钥,学生编号或学校提供空值,它将变得更加复杂。

尽管可以使用空格使用字符串连接版本,但是如果您有奇怪的数据(本身包含空格等),在奇怪的情况下它仍然会失败。上面的代码是您想要的逻辑代码...首先按报告键进行比较,然后如果报告键相同,则仅打扰学生号,等等。


6
尽管此代码没有“错误”,但我理解。我更喜欢Jason的实现,因为他只有一个return语句,因此看起来更容易理解。
jzd

当我尝试使用您的代码时,无法使用compareTo()方法。您能帮我解决问题吗?
毒蛇

@viper:好的,因为我没有实现Comparable<T>,所以我正在实现Comparator<T>。我们不知道您要实现什么,或者您尝试了什么,或者出了什么问题。在进行更多研究之后,也许您应该提出一个新问题。(它可能已经被问过了。)
乔恩·斯基特

7

我建议使用Java 8 Lambda方法:

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

5

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

}

empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));这段代码对我有所帮助。谢谢
Sumit Badaya '17

它要求使用最低Android版本
viper

4

如果StudentNumber是数字,它将不按数字排序,而是字母数字。不要指望

"2" < "11"

这将是:

"11" < "2"

这回答了为什么结果错误的实际问题。
Florian F

3

如果要先按ReportKey排序,然后按学生编号排序,然后根据学校排序,则需要比较每个字符串,而不是将它们串联。如果用空格填充字符串,使每个ReportKey的长度相同,依此类推,则该方法可能会起作用,但是这样做并不值得。相反,只需更改compare方法以比较ReportKeys,如果compareTo返回0,则尝试使用StudentNumber,然后选择School。


3

Comparator接口与JDK1.8中引入的方法comparingthenComparing或更具体的方法comparingXXX和结合使用thenComparingXXX

例如,如果我们想按人的ID先按人名排序,然后按年龄,然后按名称排序:

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

0

这是一个完整的示例,该示例比较对象中的2个字段(一个String和一个int),也使用Collat​​or进行排序。

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

}

输出:

Costrels 1039737

费用310831

费用310832

费用1570019


0

上面的很多答案都有用单个比较器方法比较的字段,但实际上不起作用。尽管对于每个字段实现了不同的比较器,但仍存在一些答案,我将其发布是因为我相信此示例将更加清楚和简单。

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



    }

}

输出值

降序排列的学生列表是[学生[bornYear = 2018,bornMonth = null,bornDay = null],学生[bornYear = 2018,bornMonth = 12,bornDay = null],学生[bornYear = 2018,bornMonth = 12,bornDay = null],学生[bornYear = 2018,bornMonth = 11,bornDay = null],学生[bornYear = 2017,bornMonth = 9,bornDay = null],学生[bornYear = 2017,bornMonth = 8,bornDay = null],学生[ bornYear = 2017,bornMonth = 6,bornDay = null],学生[bornYear = 2017,bornMonth = 4,bornDay = null],学生[bornYear = 2017,bornMonth = 2,bornDay = null],学生[bornYear = 2016,bornMonth = 8,bornDay = null]]

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.