[java] 여러 필드로 개체를 비교하는 방법

다음과 같이 비교할 수있는 여러 필드가있는 개체가 있다고 가정합니다.

public class Person {

    private String firstName;
    private String lastName;
    private String age;

    /* Constructors */

    /* Methods */

}

따라서이 예에서는 다음과 같은 질문을합니다.

a.compareTo(b) > 0

a의 성이 b보다 앞에 오거나 a가 b보다 오래된 지 등을 묻는 것일 수 있습니다.

불필요한 클러 터나 오버 헤드를 추가하지 않고 이러한 종류의 객체를 여러 번 비교할 수있는 가장 깨끗한 방법은 무엇입니까?

  • java.lang.Comparable 하나의 필드로만 비교할 수있는 인터페이스
  • (즉, 다수의 비교 방법을 추가 compareByFirstName(), compareByAge()등) 제 생각에 어수선하게됩니다.

그렇다면 가장 좋은 방법은 무엇입니까?



답변

ComparatorPerson객체 를 비교하는를 구현할 수 있으며 원하는 만큼 많은 필드를 검사 할 수 있습니다. 여러 비교기를 작성하는 것이 더 간단하지만 비교할 필드를 알려주는 변수를 비교기에 넣을 수 있습니다.


답변

자바 8 :

Comparator.comparing((Person p)->p.firstName)
          .thenComparing(p->p.lastName)
          .thenComparingInt(p->p.age);

접근 자 메서드가있는 경우 :

Comparator.comparing(Person::getFirstName)
          .thenComparing(Person::getLastName)
          .thenComparingInt(Person::getAge);

클래스가 Comparable을 구현하면 compareTo 메소드에서 이러한 비교기를 사용할 수 있습니다.

@Override
public int compareTo(Person o){
    return Comparator.comparing(Person::getFirstName)
              .thenComparing(Person::getLastName)
              .thenComparingInt(Person::getAge)
              .compare(this, o);
}


답변

구현해야합니다 Comparable <Person>. 모든 필드가 널 (null)이 아니라고 (간단하게하기 위해) 나이가 정수이고, 순위가 첫 번째, 마지막, 나이 인 경우, compareTo방법은 매우 간단합니다.

public int compareTo(Person other) {
    int i = firstName.compareTo(other.firstName);
    if (i != 0) return i;

    i = lastName.compareTo(other.lastName);
    if (i != 0) return i;

    return Integer.compare(age, other.age);
}


답변

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

작업 코드 이 요지의

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

Java 8은 람다에 의해 이것을 잘 해결합니다 (구아바와 Apache Commons는 여전히 더 많은 유연성을 제공 할 수 있습니다)

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

아래 @gaoagong의 답변 덕분에 .

지저분하고 복잡한 : 손으로 정렬

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

이 방법이 훨씬 나아지지만 가장 일반적인 사용 사례에는 보일러 플레이트 코드가 필요합니다. 기본적으로 null 값의 값을 줄여야합니다. null 필드의 경우 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의 ComparisonChain과 마찬가지로이 라이브러리 클래스는 여러 필드를 쉽게 정렬 할 수 있지만 null 값 (예 : 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));


답변

@Patrick 여러 필드를 연속적으로 정렬하려면 ComparatorChain을 시도하십시오.

ComparatorChain은 하나 이상의 Comparator를 순서대로 래핑하는 Comparator입니다. ComparatorChain은 1) 단일 Comparator가 0이 아닌 결과를 반환하고 그 결과가 반환 될 때까지 또는 2) ComparatorChain이 소진되고 0이 반환 될 때까지 각 Comparator를 순차적으로 호출합니다. 이 유형의 정렬은 SQL의 다중 열 정렬과 매우 유사하며이 클래스를 사용하면 Java 클래스가 List를 정렬 할 때 이러한 종류의 동작을 에뮬레이트 할 수 있습니다.

SQL과 같은 정렬을 더욱 용이하게하기 위해 목록의 단일 비교기 순서를 반대로 바꿀 수 있습니다.

compare (Object, Object)가 호출 된 후 새 Comparators를 추가하거나 오름차순 / 내림차순 정렬을 변경하는 메소드를 호출하면 UnsupportedOperationException이 발생합니다. 그러나 정렬 목록을 정의하는 기본 비교기 목록 또는 BitSet을 변경하지 않도록주의하십시오.

ComparatorChain 인스턴스는 동기화되지 않습니다. 클래스는 생성시 스레드로부터 안전하지 않지만 모든 설정 작업이 완료된 후 다중 비교를 수행하는 것은 안전합니다.


답변

항상 고려할 수있는 또 다른 옵션은 Apache Commons입니다. 많은 옵션을 제공합니다.

import org.apache.commons.lang3.builder.CompareToBuilder;

전의:

public int compare(Person a, Person b){

   return new CompareToBuilder()
     .append(a.getName(), b.getName())
     .append(a.getAddress(), b.getAddress())
     .toComparison();
}


답변

Comparator를 구현하는 Enum을 살펴볼 수도 있습니다.

http://tobega.blogspot.com/2008/05/beautiful-enums.html

예 :

Collections.sort(myChildren, Child.Order.ByAge.descending());