자바(SE)

Comparator의 default method chaining

  • -

Comparator의 default method chaining

Java에서 정렬을 위해서 Comparator를 사용하는데 어느 순간 다양한 static /default 메서드들이 생겨서 정렬을 훨씬 편하게 처리할 수 있게 되었다.

이 메서드들은 대부분 자기 자신을 리턴해서 chaining 형태로 사용되는데 람다식과 제네릭을 사용하면서 주의할 점이 있어서 관련 내용을 포스팅 한다.

 

Comparator의 static/default 메서드들

다음은 Comparator에 선언된 static 메서드 들이다.

메서드

선언부와 설명

naturalOrder()

public static <T extends Comparable<? super T>> Comparator<T> naturalOrder()

Comparable 타입의 T가 가진 compareTo()에 의해 오름차순 정렬하는 Comparator를 리턴한다.

reverseOrder()

public static <T extends Comparable<? super T>> Comparator<T> reverseOrder()

naturalOrder()의 역순으로 정렬하는 Comparator를 리턴한다.

comparing()

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)

FunctionT를 파라미터로 받아서 Comparable 타입의 U를 리턴할 때 U를 기준으로 정렬하는 Comparator<T>를 리턴한다.

comparingP()

public static <T> Comparator<T> comparingT(ToPFunction<? super T> keyExtractor)

comparing()에서 U의 타입이 P일 경우 별도의 오토박싱 없이 처리하는 메서드이다.

nullsFirst()

public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)

정렬 결과 null 값을 맨 처음에 위치시킨다.

nullsLast()

public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)

정렬 결과 null 값을 맨 나중에 위치시킨다.

PInt, Long, Double을 나타낸다.

메서드들에 타입 파라미터가 적용되면서 매우 복잡하게 보이고 (솔직히 복잡하다.ㅜㅜ) 한글로 설명하다 보니 말이 매우 난잡해졌다.

추가로 사용할 수 있는 default 메서드들은 아래와 같다.

메서드

선언부와 설명

reversed()

default Comparator<T> reversed()

현재 Comparator의 정렬 방식의 역순으로 정렬하는 Comparator를 리턴한다.

thenComparing()

default Comparator<T> thenComparing(Comparator<? super T> other)

1차 정렬 이후 추가적인 정렬을 위한 Comparator를 리턴한다.

thenComparingP()

default Comparator<T> thenComparingP(ToPFunction<? super T> keyExtractor)

비교 대상이 기본형인 경우 thenComparing() 대신 사용한다.

PInt, Long, Double을 나타낸다.

 

간단한 사용법

다음과 같은 리스트를 정렬시켜 보자. 

List<String> heroes = Arrays.asList("아이언맨", "헐크", "스파이더맨","블랙팬서", "닥터스트레인지");

 

여기서는 정렬 되지 않은 원본을 유지하기 위해서 Stream을 생성해서 Stream을 정렬시켜본다. 

처음은 가장 간단한 형태로 문자열의 길이 기반으로 정렬을 처리한다.

Comparator<String> lenc = Comparator.comparing(str -> str.length());

List<String> sorted1 = heroes.stream().sorted(lenc).collect(Collectors.toList());

System.out.printf("정렬1: %s%n", sorted1);

//정렬1: [헐크, 아이언맨, 블랙팬서, 스파이더맨, 닥터스트레인지]

 

이번에는 default 메서드인 thenComparing을 이용해서 2차 정렬을 시도해보자. 길이가 같다면 문자열의 자연 순인 알파벳 기준으로 정렬한다.

Comparator<String> lenc = Comparator.comparing(str -> str.length());
...
List<String> sorted2 = heroes.stream().sorted(lenc.
                                                  thenComparing(Comparator.naturalOrder()))
                                      .collect(Collectors.toList());
                
System.out.printf("정렬2: %s%n", sorted2);

// 정렬2: [헐크, 블랙팬서, 아이언맨, 스파이더맨, 닥터스트레인지]

 

Comparator를 sorted 안으로 넣어보자!!

1회성인 Comparator가 sorted 밖으로 돌아디니는게 별로 예뻐보이지 않는다. 이를 inline 스타일로 작성해보자.

첫 번째 케이스를 inline 스타일로 변경해보면...

List<String> sorted3 = heroes.stream().sorted(Comparator.comparing(str -> str.length()))
                                      .collect(Collectors.toList());
                
System.out.printf("정렬3: %s%n", sorted3);

// 정렬3: [헐크, 아이언맨, 블랙팬서, 스파이더맨, 닥터스트레인지]

 

두 번째 녀석도 inline 스타일로 변경해보면..

List<String> sorted4 = heroes.stream().sorted(Comparator.comparing(str -> str.length())
                                      .thenComparing(Comparator.naturalOrder()))
                                      .collect(Collectors.toList());
                
System.out.printf("정렬4: %s%n", sorted4);

// Cannot infer type argument(s) for <T, U> comparing(Function<? super T,? extends U>)

당연히 깔끔하게 출력될것으로 예상했지만 슬프게도 타입을 추정할 수 없다는 오류 메시지를 토해내버린다. 

 

원인

원인은 inline 스타일의 sorted 내부에서 사용된 첫번째 Comparator.comparing이 컴파일 되고 그걸 이어받아서 thenComparing이 동작하는데 첫번째의 연산에서 타입을 추정할 수 없기 때문에 발생한다.

해당 람다식인 str -> str.length() 만 보면 도대체 str이 뭐지??? 할 수밖에 없다.

반면 inline 스타일이 아닌 경우는 Comparator<String>이라고 선언되었기 때문에 이를 통해서 타입 추정이 이뤄지므로 상관 없게 된다.

 

처리 방법 1

가장 기본적인 형태는 위에서 살펴봤듯이 inline 스타일을 포기하는 것이다.

처리 방법 2

두 번째 방법은 첫 번째 메서드 호출 시 타입 파라미터를 명시해주는 것이다.

List<String> sorted5 = heroes.stream()
                .sorted(Comparator.<String, Integer>comparing(str -> str.length())
                                  .thenComparing(Comparator.naturalOrder()))
                .collect(Collectors.toList());

System.out.printf("정렬5: %s%n", sorted5);

// 정렬5: [헐크, 블랙팬서, 아이언맨, 스파이더맨, 닥터스트레인지]

 

처리방법 3

마지막 방법은 메서드 참조를 이용하는 형태도 있다.

List<String> sorted6 = heroes.stream()
                .sorted(Comparator.comparing(String::length)
                        .thenComparing(Comparator.naturalOrder()))
                .collect(Collectors.toList());
                
System.out.printf("정렬6: %s%n", sorted6);

// 정렬6: [헐크, 블랙팬서, 아이언맨, 스파이더맨, 닥터스트레인지]

 

'자바(SE)' 카테고리의 다른 글

Annotation(에너테이션) 사용법  (0) 2020.10.20
밑이 2인 로그 구하기  (0) 2020.10.16
List에서의 자료 삭제  (0) 2020.08.11
Integer 사용 시 주의점  (0) 2020.08.07
[자료구조]Queue - add vs offer  (0) 2019.12.22
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.