Java 具有多个字段的 Collections.sort

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4258700/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 15:06:23  来源:igfitidea点击:

Collections.sort with multiple fields

javasortingcollections

提问by Milli Szabo

I have a list of "Report" objects with three fields (All String type)-

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

ReportKey
StudentNumber
School

I have a sort code goes like-

我有一个排序代码就像 -

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

});

For some reason, I don't have the sorted order. One advised to put spaces in between fields, but why?

出于某种原因,我没有排序顺序。有人建议在字段之间放置空格,但为什么呢?

Do you see anything wrong with the code?

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

采纳答案by Jason S

Do you see anything wrong with the code?

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

Yes. Why are you adding the three fields together before you compare them?

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

I would probably do something like this: (assuming the fields are in the order you wish to sort them in)

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

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

回答by jzd

If you want to sort based on ReportKey first then Student Number then School, you need to compare each String instead of concatenating them. Your method might work if you pad the strings with spaces so that each ReportKey is the same length and so on, but it is not really worth the effort. Instead just change the compare method to compare the ReportKeys, if compareTo returns 0 then try StudentNumber, then School.

如果您想先根据 ReportKey 排序,然后是学生编号,然后是学校,则需要比较每个字符串而不是连接它们。如果您用空格填充字符串,以便每个 ReportKey 的长度相同等等,您的方法可能会起作用,但这并不值得付出努力。相反,只需更改比较方法来比较 ReportKeys,如果 compareTo 返回 0,则尝试 StudentNumber,然后是 School。

回答by Jon Skeet

If you want to sort by report key, then student number, then school, you should do something like this:

如果你想按报告键排序,然后是学生号,然后是学校,你应该这样做:

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

This assumes none of the values can be null, of course - it gets more complicated if you need to allow for null values for the report, report key, student number or school.

当然,这假定所有值都不能为空 - 如果您需要为报告、报告键、学生编号或学校允许空值,则情况会变得更加复杂。

While you couldget the string concatenation version to work using spaces, it would still fail in strange cases if you had odd data which itself included spaces etc. The above code is the logicalcode you want... compare by report key first, then only bother with the student number if the report keys are the same, etc.

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

回答by FrVaBe

If the StudentNumber is numeric it will not be sorted numeric but alphanumeric. Do not expect

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

"2" < "11"

it will be:

这将是:

"11" < "2"

回答by ColinD

I'd make a comparator using Guava's ComparisonChain:

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

回答by Benny Bottema

(originally from Ways to sort lists of objects in Java based on multiple fields)

(最初来自Ways to sort list of objects in Java based on multiple fields

Original working code in this gist

此要点中的原始工作代码

Using Java 8 lambda's (added April 10, 2019)

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

Java 8 solves this nicely by lambda's (though Guava and Apache Commons might still offer more flexibility):

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

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

Thanks to @gaoagong's answer below.

感谢@gaoagong 在下面回答

Note that one advantage here is that the getters are evaluated lazily (eg. getSchool()is only evaluated if relevant).

请注意,这里的一个优点是 getter 被懒惰地评估(例如getSchool(),仅在相关时才评估)。

Messy and convoluted: Sorting by 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);  
    }  
});  

This requires a lot of typing, maintenance and is error prone. The only advantage is that gettersare only invoked when relevant.

这需要大量打字、维护并且容易出错。唯一的优点是getter仅在相关时才被调用。

The reflective way: Sorting with BeanComparator

反射方式:用BeanComparator排序

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

Collections.sort(pizzas, chain);  

Obviously this is more concise, but even more error prone as you lose your direct reference to the fields by using Strings instead (no typesafety, auto-refactorings). Now if a field is renamed, the compiler won't even report a problem. Moreover, because this solution uses reflection, the sorting is much slower.

显然,这更简洁,但更容易出错,因为您通过使用字符串(无类型安全、自动重构)而丢失了对字段的直接引用。现在,如果一个字段被重命名,编译器甚至不会报告问题。而且,因为这个解决方案使用反射,所以排序要慢得多。

Getting there: Sorting with Google Guava's ComparisonChain

到达那里:使用 Google Guava 的比较链进行排序

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

This is much better, but requires some boiler plate code for the most common use case: null-values should be valued less by default. For null-fields, you have to provide an extra directive to Guava what to do in that case. This is a flexible mechanism if you want to do something specific, but often you want the default case (ie. 1, a, b, z, null).

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

And as noted in the comments below, these getters are all evaluated immediately for each comparison.

正如下面的评论中所指出的,每次比较都会立即评估这些 getter。

Sorting with Apache Commons CompareToBuilder

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

Like Guava's ComparisonChain, this library class sorts easily on multiple fields, but also defines default behavior for null values (ie. 1, a, b, z, null). However, you can't specify anything else either, unless you provide your own Comparator.

与 Guava 的比较链一样,这个库类可以轻松地对多个字段进行排序,但也定义了空值(即 1、a、b、z、null)的默认行为。但是,您也不能指定任何其他内容,除非您提供自己的 Comparator。

Again, as noted in the comments below, these getters are all evaluated immediately for each comparison.

同样,如以下评论中所述,每次比较时都会立即评估这些吸气剂。

Thus

因此

Ultimately it comes down to flavor and the need for flexibility (Guava's ComparisonChain) vs. concise code (Apache's CompareToBuilder).

最终归结为风格和对灵活性的需求(Guava 的比较链)与简洁的代码(Apache 的 CompareToBuilder)。

Bonus method

奖金方式

I found a nice solution that combines multiple comparators in order of priority on CodeReviewin a MultiComparator:

我找到了一个很好的解决方案,它按照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));
    }
}

Ofcourse Apache Commons Collections has a util for this already:

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

ComparatorUtils.chainedComparator(comparatorCollection)

ComparatorUtils.chainedComparator(comparatorCollection)

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

回答by gaoagong

This is an old question so I don't see a Java 8 equivalent. Here is an example for this specific case.

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

回答by Lakshman Miani

Sorting with multiple fields in Java8

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

}

回答by live-love

Here is a full example comparing 2 fields in an object, one String and one int, also using Collator to sort.

这是一个比较对象中的 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;
        }       
    }

}

Output:

输出:

costrels 1039737

成本 1039737

costs 310831

费用 310831

costs 310832

费用 310832

Costs 1570019

费用 1570019

回答by Zia Ul Mustafa

I suggest to use Java 8 Lambda approach:

我建议使用 Java 8 Lambda 方法:

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