Collections.sort з кількома полями


83

У мене є список об'єктів "Report" із трьома полями (тип 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

Фіксована довжина. Вибачте забув згадати.
Milli Szabo

Відповіді:


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

Будь ласка, уточніть. Як мені тоді це зробити? Дякую.
Milli Szabo

Привіт, ти можеш додати більше, якщо (c == 0)? Я не знаю, чи правильно, але, здається, ні, тому що, якщо перша умова дотримана, ніколи не входить до другої чи третьої .. і т.

10
Я не думаю, що ви зрозуміли a.compareTo(b); домовленість полягає в тому, що значення 0 представляє рівність, цілі від'ємні числа представляють це, a < bа цілі позитивні числа - це значення a > bдля Comparable aі b.
Jason S

1
Не працює. Я спробував це, і це не працює. Він працює лише з однимcompareTo
shimatai

110

(родом із « Способи сортування списків об’єктів у 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);  

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

Як дістатися: сортування за допомогою 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(); 
        */  
    }  
});  

Це набагато краще, але для найпоширенішого випадку використання потрібен певний код котла: нульові значення за замовчуванням повинні оцінюватися менше. Для нульових полів потрібно надати додаткову директиву 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's ComparisonChain, цей клас бібліотеки легко сортує в декількох полях, але також визначає поведінку за замовчуванням для нульових значень (тобто. 1, a, b, z, null). Однак ви також не можете вказати нічого іншого, якщо ви не надаєте власний компаратор.

Знову ж таки, як зазначено у коментарях нижче, усі ці одержувачі оцінюються негайно для кожного порівняння.

Таким чином

Врешті-решт це зводиться до смаку та необхідності гнучкості (Guava's ComparisonChain) проти стислого коду (Apache's 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 лямбда
відвертий

дякую за чудову відповідь Бенні. У мене є сценарій, коли властивість не знаходиться безпосередньо всередині мого об’єкта. але є вкладений об’єкт. як мені робити в такому випадку ?? як, наприклад, тут 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();
  }
}

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
Хоча в цьому коді немає "помилок", і я це розумію. Я віддаю перевагу реалізації Джейсона, оскільки, здається, легше дотримуватися, оскільки він має лише одне твердження 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));цей код мені допоміг. Дякую
Суміт Бадая

Він просить використовувати мінімальну версію для Android 24.
viper

4

Якщо StudentNumber числовий, він не буде відсортований числовим, а буквено-цифровим. Не чекайте

"2" < "11"

це буде:

"11" < "2"

Це відповідає на актуальне питання, чому результат неправильний.
Флоріан F

3

Якщо ви хочете відсортувати спочатку на основі ReportKey, а потім номер студента, а потім школу, вам потрібно порівняти кожен рядок, а не об'єднувати їх. Ваш метод може працювати, якщо ви заповнюєте рядки пробілами так, щоб кожен ReportKey був однакової довжини тощо, але насправді це не варто витрачати зусилля. Натомість просто змініть метод порівняння, щоб порівняти ReportKeys, якщо compareTo повертає 0, спробуйте StudentNumber, а потім School.


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

Ось повний приклад порівняння 2 полів в об'єкті, одного рядка та одного 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;
        }       
    }

}

Вихід:

видатки 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, день народження = нуль]]

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.