Collections.sort с несколькими полями

83

У меня есть список объектов «Отчет» с тремя полями (тип All String) -

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
Фиксированная длина. Извините, забыл упомянуть.
Милли Сабо

Ответы:

138

Вы видите что-то не так с кодом?

Да. Почему вы складываете три поля вместе, прежде чем сравнивать их?

Я бы, вероятно, сделал что-то вроде этого: (при условии, что поля находятся в том порядке, в котором вы хотите их отсортировать)

@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;
}
Джейсон С
источник
Пожалуйста, дополните. Как мне тогда это сделать? Благодарю.
Милли Сабо
Привет, можно еще добавить if (c == 0)? Я не знаю, правильно ли это, но кажется, что нет, потому что, если первое условие выполнено, никогда не войдет во второе или третье… и т. Д.
10
Я не думаю, что вы поняли a.compareTo(b); соглашение состоит в том, что значение 0 представляет равенство, отрицательные целые числа представляют это, a < bа положительные целые числа представляют это a > bдля Comparable aи b.
Jason S
1
Это не работает. Я пробовал это, и это не работает. Работает только с однимcompareTo
shimatai
110

(изначально из Ways для сортировки списков объектов в Java на основе нескольких полей )

Исходный рабочий код в этом смысле

Использование лямбда-выражений Java 8 (добавлено 10 апреля 2019 г.)

Java 8 прекрасно решает эту проблему с помощью лямбды (хотя 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);  
    }  
});  

Это требует много ввода, обслуживания и подвержено ошибкам. Единственное преимущество в том, что геттеры вызываются только при необходимости.

Рефлексивный способ: сортировка с помощью BeanComparator

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

Collections.sort(pizzas, chain);  

Очевидно, это более лаконично, но еще больше подвержено ошибкам, поскольку вы теряете прямую ссылку на поля, используя вместо этого строки (без обеспечения безопасности типов, авторефакторинга). Теперь, если поле будет переименовано, компилятор даже не сообщит о проблеме. Более того, поскольку это решение использует отражение, сортировка выполняется намного медленнее.

Как добраться: сортировка с помощью 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(); 
        */  
    }  
});  

Это намного лучше, но для наиболее распространенного варианта использования требуется некоторый шаблонный код: по умолчанию нулевые значения должны иметь меньшее значение. Для нулевых полей вы должны предоставить 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();  
    }  
});  

Как и ComparisonChain Guava, этот библиотечный класс легко сортирует по нескольким полям, но также определяет поведение по умолчанию для нулевых значений (например, 1, a, b, z, null). Однако вы также не можете указать что-либо еще, если не предоставите свой собственный Comparator.

Опять же, как отмечено в комментариях ниже, все эти геттеры немедленно оцениваются для каждого сравнения.

Таким образом

В конечном итоге все сводится к вкусу и необходимости гибкости (ComparisonChain Guava) по сравнению с кратким кодом (CompareToBuilder от Apache).

Бонусный метод

Я нашел хорошее решение, которое объединяет несколько компараторов в порядке приоритета на 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
отлично работает с лямбда-
frank
спасибо за отличный ответ Бенни. У меня есть сценарий, в котором свойство не находится непосредственно внутри моего объекта. но есть вложенный объект. как мне поступить в таком случае ?? как, например, здесь Collections.sort (reportList, Comparator.comparing (Report :: getReportKey) .thenComparing (Report :: getStudentNumber) .thenComparing (Report :: getSchool)); Внутри объекта отчета у меня есть объект студента, а затем внутри объекта студента у меня есть номер студента. как нам это отсортировать в таком случае? любая помощь будет оценена.
Мадху Редди
@MadhuReddy В этом примере ссылки на методы используются как лямбда-выражения, но вы можете просто предоставить правильную лямбду, которая вместо этого возвращает соответствующее вложенное поле.
Бенни Боттема
44

Я бы сделать компаратор с помощью гуавы «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();
  }
}
ColinD
источник
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 + '\'' +
                   '}';
        }
    }
}
Гаоагонг
источник
comparingи thenComparingна победу!
asgs
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());
    }
}

Это, конечно, предполагает, что ни одно из значений не может быть нулевым - это усложняется, если вам нужно разрешить нулевые значения для отчета, ключа отчета, номера ученика или школы.

Хотя вы можете заставить версию конкатенации строк работать с использованием пробелов, она все равно не сработает в странных случаях, если у вас были нечетные данные, которые сами включали пробелы и т. Д. Приведенный выше код - это логический код, который вы хотите ... сначала сравните по ключу отчета, затем беспокоиться о номере ученика, только если ключи отчета совпадают и т. д.

Джон Скит
источник
6
Хотя в этом коде нет ничего «неправильного» и я это понимаю. Я предпочитаю реализацию Джейсона, потому что за ней легче следить, поскольку у него есть только один оператор возврата.
jzd
Когда я пытаюсь использовать ваш код, он не может использовать compareTo()метод. Не могли бы вы помочь мне решить проблему.
viper
@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));этот код мне помог. Спасибо
Сумит Бадая
Требуется минимальная версия Android 24.
viper
4

Если StudentNumber числовой, он будет сортироваться не по цифрам, а по буквенно-цифровым. Не ждите

"2" < "11"

это будет:

"11" < "2"
FrVaBe
источник
Это отвечает на актуальный вопрос, почему результат неверен.
Florian F
3

Если вы хотите отсортировать сначала по ReportKey, затем по номеру ученика, а затем по школе, вам нужно сравнить каждую строку, а не объединять их. Ваш метод может работать, если вы дополните строки пробелами, чтобы все ReportKey имел одинаковую длину и т. Д., Но на самом деле это не стоит усилий. Вместо этого просто измените метод compare, чтобы сравнить ReportKeys, если compareTo возвращает 0, попробуйте StudentNumber, затем School.

jzd
источник
3

Используйте Comparatorинтерфейс с методами, представленными в JDK1.8: comparingи thenComparing, или более конкретными методами: comparingXXXиthenComparingXXX .

Например, если мы хотим отсортировать список людей сначала по идентификатору, затем по возрасту, а затем по имени:

            Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
                    .thenComparingInt(Person::getAge)
                    .thenComparing(Person::getName);
            personList.sort(comparator);
щенок
источник
0

Вот полный пример сравнения двух полей в объекте, одного String и одного int, также с использованием Collator для сортировки.

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



    }

}

ВЫВОД

Отсортированный список студентов в порядке убывания: [Student [bornYear = 2018 ,bornMonth = null ,bornDay = null], Student [bornYear = 2018 ,bornMonth = 12 ,bornDay = null], Student [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]]

Насир Мохаммад
источник