여러 필드가있는 Collections.sort


83

세 개의 필드 (모든 문자열 유형)가있는 "보고서"개체 목록이 있습니다.

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 2010

고정 길이. 언급하는 것을 잊었다.
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

안녕하세요, if (c == 0)? 옳은지 모르겠지만 그렇지 않은 것 같습니다. 첫 번째 조건이 충족되면 두 번째 또는 세 번째 .. etc ..

10
이해하지 못했다고 생각합니다 a.compareTo(b). 관례는 0의 값은 같음을 나타내고, 음의 정수는 그것을 나타내고 a < b, 양의 정수는 및 에 a > b대한 것을 나타냅니다 .Comparable ab
Jason S

1
작동하지 않습니다. 나는 그것을 시도했지만 작동하지 않습니다. 하나만 작동합니다compareTo
shimatai

110

(원래 여러 필드를 기반으로 Java에서 객체 목록을 정렬 하는 방법에서 )

이 요점의 원래 작업 코드

Java 8 람다 사용 (2019 년 4 월 10 일 추가됨)

Java 8은 Lambda로이 문제를 훌륭하게 해결합니다 (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);  
    }  
});  

이것은 많은 타이핑, 유지 보수가 필요하며 오류가 발생하기 쉽습니다. 유일한 장점은 getter 가 관련이있을 때만 호출 .

반사적 방식 : 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(); 
        */  
    }  
});  

이것은 훨씬 더 좋지만 가장 일반적인 사용 사례에 대해 약간의 상용구 코드가 필요합니다. null 값은 기본적으로 더 작은 값이어야합니다. 널 필드의 경우 Guava에이 경우 수행 할 작업에 대한 추가 지시문을 제공해야합니다. 특정 작업을 수행하려는 경우 유연한 메커니즘이지만 종종 기본 케이스 (예 : 1, a, b, z, null)를 원합니다.

그리고 아래 주석에서 언급했듯이 이러한 getter는 각 비교에 대해 모두 즉시 평가됩니다.

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의 ComparisonChain과 마찬가지로이 라이브러리 클래스는 여러 필드에서 쉽게 정렬되지만 null 값 (예 : 1, a, b, z, null)에 대한 기본 동작도 정의합니다. 그러나 자신의 Comparator를 제공하지 않는 한 다른 것도 지정할 수 없습니다.

다시 말하지만, 아래 주석에서 언급했듯이 이러한 getter는 각 비교에 대해 모두 즉시 평가됩니다.

그러므로

궁극적으로 그것은 풍미와 유연성 (Guava의 ComparisonChain) 대 간결한 코드 (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 (comparatorCollection)

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

깨끗한 코드에 대한 완벽한 솔루션과는 너무 목적을 제공
cryptonkid

자바 8 람다와 큰
프랭크

훌륭한 답변 Benny에 감사드립니다. 속성이 내 개체 내부에 직접 있지 않은 시나리오가 있습니다. 그러나 중첩 된 개체가 있습니다. 이 경우 어떻게해야합니까 ?? 예를 들어, 여기 Collections.sort (reportList, Comparator.comparing (Report :: getReportKey) .thenComparing (Report :: getStudentNumber) .thenComparing (Report :: getSchool)); 보고서 개체 내부에는 학생 개체가 있고 학생 개체 내부에는 학생 번호가 있습니다. 이 경우 여기에서 어떻게 분류합니까? 어떤 도움을 주시면 감사하겠습니다.
Madhu Reddy

@MadhuReddy 예제에서 메서드 참조는 람다로 사용되지만 대신 적절한 중첩 필드를 반환하는 적절한 람다를 제공 할 수 있습니다.
Benny Bottema

44

내가 사용 비교기 할 것 구아바 의를 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
그것은 24 최소한의 안드로이드 버전을 요청
독사

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

이것은 물론 어떤 값도 null이 될 수 없다고 가정합니다. 보고서, 보고서 키, 학생 번호 또는 학교에 대해 null 값을 허용해야하는 경우 더 복잡해집니다.

공백을 사용하여 작동하도록 문자열 연결 버전을 얻을 있지만 , 자체적으로 공백 등을 포함하는 이상한 데이터가있는 경우 이상한 경우에 여전히 실패합니다. 위 코드는 원하는 논리 코드입니다. 먼저 보고서 키로 비교 한 다음 보고서 키가 동일한 경우에만 학생 번호로 귀찮게합니다.


6
이 코드에 "잘못된"것은 없지만 이해합니다. 저는 제이슨의 구현을 선호합니다. 왜냐하면 그는 return 문이 하나뿐이기 때문에 따라 가기가 더 쉬워 보이기 때문입니다.
jzd 2010

귀하의 코드를 사용하려고 할 때 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));이 코드가 도움이되었습니다. 감사합니다
Sumit Badaya 2017 년

그것은 최소 안드로이드 버전 24를 사용하도록 요청합니다.
viper

4

StudentNumber가 숫자이면 숫자가 아닌 영숫자로 정렬됩니다. 기대하지 마십시오

"2" < "11"

다음과 같습니다.

"11" < "2"

이것은 결과가 잘못된 이유에 대한 실제 질문에 대한 대답입니다.
Florian F

3

ReportKey를 기준으로 정렬 한 다음 Student Number, School을 기준으로 정렬하려면 각 문자열을 연결하는 대신 비교해야합니다. 각 ReportKey의 길이가 같도록 문자열을 공백으로 채우면 메서드가 작동 할 수 있지만 실제로 노력할 가치는 없습니다. 대신 비교 메서드를 변경하여 ReportKeys를 비교하고 compareTo가 0을 반환하면 StudentNumber를 시도한 다음 School을 시도하십시오.


3

ComparatorJDK1.8 : comparingthenComparing또는보다 구체적인 메소드 : comparingXXX및에 도입 된 메소드와 함께 인터페이스를 사용하십시오 thenComparingXXX.

예를 들어, 먼저 ID, 나이, 이름으로 사람 목록을 정렬하려면 :

            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], Student [bornYear = 2018, bornMonth = 11, bornDay = null], Student [bornYear = 2017, bornMonth = 9, bornDay = null], Student [bornYear = 2017, bornMonth = 8, bornDay = null], Student [ bornYear = 2017, bornMonth = 6, bornDay = null], Student [bornYear = 2017, bornMonth = 4, bornDay = null], Student [bornYear = 2017, bornMonth = 2, bornDay = null], Student [bornYear = 2016, bornMonth = 8, bornDay = null]]

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.