Collections.sort с несколькими полями - PullRequest
64 голосов
/ 23 ноября 2010

У меня есть список объектов «Отчет» с тремя полями (тип 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());
      }

});

По какой-то причине у меня нет отсортированного заказа. Рекомендуется ставить пробелы между полями, но почему?

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

Ответы [ 12 ]

110 голосов
/ 23 ноября 2010

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

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

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

@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;
}
52 голосов
/ 20 ноября 2013

(из Способы сортировки списков объектов в Java на основе нескольких полей )

Рабочий код в суть

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

Java 8 хорошо решает эту проблему с помощью лямбды (хотя Guava и Apache Commons могут по-прежнему предлагать большую гибкость):

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

Благодаря @ gaoagong's ответ ниже .

Грязно и запутанно: сортировка вручную

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

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

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

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

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

В конечном итоге все сводится к вкусу и необходимости в гибкости (ComparisonChain от Guava) в сравнении с лаконичным кодом(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));
    }
}

Ofcourse Apache Commons Collections уже имеет утилиту для этого:

ComparatorUtils.chainedComparator (comptorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
44 голосов
/ 23 ноября 2010

Я бы сделал компаратор, используя Гуава х 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();
  }
}
17 голосов
/ 02 апреля 2016

Это старый вопрос, поэтому я не вижу эквивалента 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 + '\'' +
                   '}';
        }
    }
}
13 голосов
/ 23 ноября 2010

Если вы хотите отсортировать по ключу отчета, затем по номеру учащегося, затем по школе, вам следует сделать что-то вроде этого:

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 голосов
/ 31 июля 2018

Предлагаю использовать Java 8 Лямбда-подход:

List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));
5 голосов
/ 16 января 2017

Сортировка с несколькими полями в 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 + "]";
    }

}
3 голосов
/ 07 ноября 2018

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

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

            Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
                    .thenComparingInt(Person::getAge)
                    .thenComparing(Person::getName);
            personList.sort(comparator);
3 голосов
/ 23 ноября 2010

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

"2" < "11"

это будет:

"11" < "2"
3 голосов
/ 23 ноября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...