Collections.sort với nhiều trường


83

Tôi có một danh sách các đối tượng "Báo cáo" với ba trường (Tất cả loại Chuỗi) -

ReportKey
StudentNumber
School

Tôi có một mã sắp xếp giống như-

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

});

Vì một số lý do, tôi không có thứ tự được sắp xếp. Một người khuyên nên đặt khoảng trắng ở giữa các trường, nhưng tại sao?

Bạn có thấy điều gì sai với mã không?


Chúng có phải là các trường có độ dài cố định không? Điều gì xảy ra nếu record1.getReportKey () là "AB" và record1.getStudentNumber () là "CD", nhưng record2.getReportKey () là "ABCD"?
mellamokb

Chiều dài cố định. Xin lỗi quên đề cập.
Milli Szabo

Câu trả lời:


136

Bạn có thấy điều gì sai với mã không?

Đúng. Tại sao bạn lại thêm ba trường với nhau trước khi so sánh chúng?

Tôi có thể sẽ làm điều gì đó như thế này: (giả sử các trường theo thứ tự bạn muốn sắp xếp chúng)

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

Xin hãy giải thích. Làm thế nào để làm điều đó sau đó? Cảm ơn.
Milli Szabo

Xin chào, bạn có thể bổ sung thêm if (c == 0)? Tôi không biết nếu là đúng nhưng dường như không có, bởi vì nếu điều kiện đầu tiên được đáp ứng không bao giờ đi vào thứ hai hoặc thứ ba .. vv ..

10
Tôi không nghĩ rằng bạn đã hiểu a.compareTo(b); quy ước là giá trị 0 đại diện cho bằng nhau, số nguyên âm đại diện cho điều đó a < bvà số nguyên dương đại diện a > bcho Comparable ab.
Jason S

1
Nó không hoạt động. Tôi đã thử điều đó và nó không hoạt động. Nó hoạt động với chỉ có mộtcompareTo
shimatai

107

(nguyên gốc từ Cách sắp xếp danh sách các đối tượng trong Java dựa trên nhiều trường )

Mã làm việc ban đầu trong ý chính này

Sử dụng Java 8 lambda's (thêm vào ngày 10 tháng 4 năm 2019)

Java 8 giải quyết vấn đề này một cách độc đáo nhờ lambda's (mặc dù Guava và Apache Commons vẫn có thể cung cấp tính linh hoạt hơn):

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

Cảm ơn câu trả lời của @ gaoagong dưới đây .

Lưu ý rằng một lợi thế ở đây là getters được đánh giá một cách lười biếng (ví dụ: getSchool()chỉ được đánh giá nếu có liên quan).

Lộn xộn và phức tạp: Phân loại bằng tay

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

Điều này đòi hỏi nhiều đánh máy, bảo trì và dễ xảy ra lỗi. Ưu điểm duy nhất là getters chỉ được gọi khi có liên quan.

Cách phản chiếu: Sắp xếp bằng BeanComparator

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

Collections.sort(pizzas, chain);  

Rõ ràng là điều này ngắn gọn hơn, nhưng thậm chí dễ xảy ra lỗi hơn khi bạn mất tham chiếu trực tiếp đến các trường bằng cách sử dụng Chuỗi thay thế (không có an toàn kiểu chữ, tự động tái cấu trúc). Bây giờ nếu một trường được đổi tên, trình biên dịch thậm chí sẽ không báo cáo sự cố. Hơn nữa, vì giải pháp này sử dụng phản xạ, nên việc sắp xếp chậm hơn nhiều.

Đến đó: Sắp xếp với Google Guava's 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(); 
        */  
    }  
});  

Điều này tốt hơn nhiều, nhưng yêu cầu một số mã tấm lò hơi cho trường hợp sử dụng phổ biến nhất: giá trị null nên được đánh giá thấp hơn theo mặc định. Đối với trường null, bạn phải cung cấp thêm một chỉ thị cho Guava những việc cần làm trong trường hợp đó. Đây là một cơ chế linh hoạt nếu bạn muốn làm điều gì đó cụ thể, nhưng thường thì bạn muốn trường hợp mặc định (ví dụ: 1, a, b, z, null).

Và như đã lưu ý trong các bình luận bên dưới, những getters này đều được đánh giá ngay lập tức cho mỗi lần so sánh.

Sắp xếp với 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();  
    }  
});  

Giống như ComparisonChain của Guava, lớp thư viện này sắp xếp dễ dàng trên nhiều trường, nhưng cũng xác định hành vi mặc định cho các giá trị null (ví dụ: 1, a, b, z, null). Tuy nhiên, bạn cũng không thể chỉ định bất kỳ điều gì khác, trừ khi bạn cung cấp Bộ so sánh của riêng mình.

Một lần nữa, như đã lưu ý trong các bình luận bên dưới, những getters này đều được đánh giá ngay lập tức cho mỗi lần so sánh.

Như vậy

Cuối cùng, nó phụ thuộc vào hương vị và nhu cầu về tính linh hoạt (Guava's ComparisonChain) so với mã ngắn gọn (Apache's CompareToBuilder).

Phương thức thưởng

Tôi đã tìm thấy một giải pháp tuyệt vời kết hợp nhiều trình so sánh theo thứ tự ưu tiên trên CodeReview trong một 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));
    }
}

Tất nhiên Apache Commons Collections đã có một ứng dụng cho việc này:

ComparatorUtils.chainedComparator (bộ so sánhCollection)

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

Giải pháp hoàn hảo cho mã sạch và phục vụ mục đích quá
cryptonkid

tuyệt vời với java 8 lambda
Frank

cảm ơn vì câu trả lời tuyệt vời Benny. Tôi có một kịch bản trong đó thuộc tính không trực tiếp bên trong đối tượng của tôi. nhưng có một đối tượng lồng nhau. làm thế nào để làm trong trường hợp đó ?? như ví dụ, tại đây Collections.sort (reportList, Comparator.comparing (Report :: getReportKey) .thenComparing (Report :: getStudentNumber) .thenComparing (Report :: getSchool)); Bên trong Đối tượng Báo cáo tôi có một đối tượng học sinh và sau đó bên trong đối tượng học sinh, tôi có một số học sinh. làm thế nào để chúng tôi sắp xếp điều đó ở đây trong trường hợp đó? Bất kỳ trợ giúp sẽ được đánh giá cao.
Madhu Reddy

@MadhuReddy Trong ví dụ, phương thức tham chiếu được sử dụng như lambda, nhưng bạn chỉ có thể cung cấp một lambda thích hợp để trả về trường lồng nhau thích hợp.
Benny Bottema

44

Tôi muốn làm một so sánh sử dụng ổi '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();
  }
}

20

Đây là một câu hỏi cũ nên tôi không thấy tương đương với Java 8. Đây là một ví dụ cho trường hợp cụ thể này.

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để giành chiến thắng!
asgs

1
Nó yêu cầu tối thiểu cho android phiên bản 24.
viper

14

Nếu bạn muốn sắp xếp theo khóa báo cáo, sau đó là mã số sinh viên, sau đó là trường học, bạn nên làm như sau:

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

Điều này giả định rằng không có giá trị nào có thể là null, tất nhiên - sẽ phức tạp hơn nếu bạn cần cho phép các giá trị null cho báo cáo, khóa báo cáo, số sinh viên hoặc trường học.

Mặc dù bạn có thể làm cho phiên bản nối chuỗi hoạt động bằng cách sử dụng dấu cách, nhưng nó vẫn sẽ không thành công trong những trường hợp kỳ lạ nếu bạn có dữ liệu lẻ mà bản thân nó bao gồm dấu cách, v.v. Đoạn mã trên là mã logic bạn muốn ... hãy so sánh bằng khóa báo cáo trước, sau đó chỉ bận tâm với số sinh viên nếu các khóa báo cáo giống nhau, v.v.


6
Mặc dù không có "sai" với mã này và tôi hiểu nó. Tôi thích cách triển khai của Jason hơn vì nó có vẻ dễ theo dõi hơn vì anh ấy chỉ có một câu lệnh trả lại.
jzd

Khi tôi cố gắng sử dụng mã của bạn, nó không thể sử dụng compareTo()phương pháp. Bạn có thể vui lòng giúp tôi giải quyết vấn đề.
viper

@viper: À không, vì tôi không triển khai Comparable<T>, tôi đang triển khai Comparator<T>. Chúng tôi không biết bạn đang cố gắng đạt được điều gì, bạn đã cố gắng điều gì, hay điều gì đã xảy ra. Có lẽ bạn nên đặt một câu hỏi mới, có thể là sau khi nghiên cứu thêm. (Nó có thể cũng đã được yêu cầu rồi.)
Jon Skeet

7

Tôi đề nghị sử dụng cách tiếp cận Lambda của Java 8:

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

5

Sắp xếp với nhiều trường trong 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));mã này đã giúp tôi. Cảm ơn
Sumit Badaya

Nó yêu cầu phải sử dụng tối thiểu android phiên bản 24.
viper

4

Nếu StudentNumber là số, nó sẽ không được sắp xếp là số mà là chữ và số. Không mong đợi

"2" < "11"

nó sẽ là:

"11" < "2"

Điều này trả lời câu hỏi thực tế là tại sao kết quả lại sai.
Florian F

3

Nếu bạn muốn sắp xếp dựa trên ReportKey trước tiên sau đó là Số sinh viên rồi đến Trường, bạn cần so sánh từng Chuỗi thay vì nối chúng. Phương pháp của bạn có thể hoạt động nếu bạn chèn các chuỗi bằng dấu cách để mỗi ReportKey có cùng độ dài, v.v., nhưng nó không thực sự đáng để nỗ lực. Thay vào đó, chỉ cần thay đổi phương thức so sánh để so sánh các ReportKeys, nếu CompareTo trả về 0 thì hãy thử StudentNumber, sau đó là School.


3

Sử dụng Comparator giao diện với các phương pháp được giới thiệu trong JDK1.8:comparingthenComparing, hoặc các phương pháp cụ thể hơn: comparingXXXthenComparingXXX .

Ví dụ: nếu chúng ta muốn sắp xếp danh sách những người theo id của họ, trước tiên là tuổi, sau đó đặt tên:

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

0

Dưới đây là một ví dụ đầy đủ so sánh 2 trường trong một đối tượng, một Chuỗi và một int, cũng sử dụng Collator để sắp xếp.

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

}

Đầu ra:

giá 1039737

giá 310831

giá 310832

Chi phí 1570019


0

Rất nhiều câu trả lời ở trên có các trường được so sánh trong phương thức so sánh đơn không thực sự hoạt động. Có một số câu trả lời mặc dù với các bộ so sánh khác nhau được triển khai cho từng trường, tôi đăng bài này vì ví dụ này sẽ rõ ràng hơn và dễ hiểu hơn mà tôi tin tưởng.

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



    }

}

ĐẦU RA

Danh sách sinh viên được sắp xếp theo thứ tự giảm dần là [Student [sinhYear = 2018, sinhMonth = null, sinhDay = null], Sinh viên [sinh ra năm = 2018, sinhMang = 12, sinhDay = null], Sinh viên [sinh năm tháng = 2018, sinh raMonth = 12, sinhDay = null], Student [sinhYear = 2018, sinhMonth = 11, sinhDay = null], Học sinh [sinh năm = 2017, sinhMonth = 9, sinhDay = null], Học sinh [sinh raYear = 2017, sinhMonth = 8, sinhDay = null], Học sinh [ BornYear = 2017, sinhMonth = 6, sinhDay = null], Sinh viên [sinhY năm = 2017, sinhMonth = 4, sinhDay = null], Sinh viên [sinh nămnăm = 2017, sinhMang = 2, sinhDay = null], Sinh viên [sinh năm = 2016, sinh tháng = 8, sinhDay = null]]

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.