Stream 클래스의 sorted 메서드

Stream의 sorted와 Comparator의 comparingInt

Stream 클래스의 sorted 메서드와 Comparator의 comparingInt 코드를 까보면서 stream과 generic 그리고 lambda를 더 깊이 공부하는 시간을 가졌습니다.

 

살펴 볼 부분

  • Stream의 sorted메서드
Stream<T> sorted(Comparator<? super T> comparator);
  • Comparator의 comparingInt메서드
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
  • Comparator Functional Interface
@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);
    boolean equals(Object obj);
    ~~~
}

 

예제 코드

두 메서드를 이용하여 간단한 예제를 만들어 보겠습니다.
음료 클래스가 있고, 음료 리스트를 만들었습니다. 음료 리스트를 가격 기준으로 오름차순으로 정렬합니다.

public class GenericWildCardExample {
    public static void main(String[] args) {
        List<Beverage> beverages = Arrays.asList(new Beverage(3000), new Beverage(2000), new Beverage(10000), new Beverage(4000));

        List<Beverage> sortedBeverages = beverages.stream().sorted(Comparator.comparingInt(Beverage::getPrice))
            .collect(toList());

        for (Beverage sortedBeverage : sortedBeverages) {
            System.out.println("sortedBeverage.getPrice() = " + sortedBeverage.getPrice());
        }
    }
}

class Beverage{
    private int price;

    public Beverage(int price) {
        this.price = price;
    }

    public int getPrice() {
        return price;
    }
}
결과 출력
sortedBeverage.getPrice() = 2000
sortedBeverage.getPrice() = 3000
sortedBeverage.getPrice() = 4000
sortedBeverage.getPrice() = 10000

 

코드 안으로

가장 먼저 볼 곳은 Stream의 sorted()입니다. sorted 매서드의 매개변수를 보면 Comparator<? super T> comparator임을 확인할 수 있습니다. 즉 T 또는 T의 부모 클래스를 Generic 타입으로 가지는 Comparator를 매개변수로 받고 있습니다.

Stream<T> sorted(Comparator<? super T> comparator);
(이 코드가 어렵다면 Generic - WildCard를 공부해보세요)

 

이제 Comparator를 살펴보겠습니다.

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);
    boolean equals(Object obj);
    
    ~~~
}

Comparator는 함수형 인터페이스입니다. 이 코드를 읽다 궁금한 점이 생겼습니다.

함수형 인터페이스에 왜 추상 메서드가 2개나 있지..??

여기서 equals는 사실 Object 클래스의 메서드입니다. 따라서 Comparator 인터페이스의 추상 메서드는 compare하나입니다.

참고) https://stackoverflow.com/questions/23721759/functionalinterface-comparator-has-2-abstract-methods

 

다시 본론으로 돌아가겠습니다.

sorted 메서드의 매개변수는 Comparator이고 Comparator는 함수형 인터페이스이므로 sorted 메서드의 매개변수는
(T o1, T o2) -> {return int a}형태의 람다식이 되어야합니다.

그렇다면 예제 코드에서 sorted안에 Comparator.comparingInt는 어떻게 sorted의 인자로 들어갈 수 있었을까요?

List<Beverage> sortedBeverages = beverages.stream().sorted(Comparator.comparingInt(Beverage::getPrice))
            .collect(toList());

Comparator.comparingInt의 반환형을 살펴보면 그 이유를 알 수 있습니다.

    public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
    }

comparingInt를 보면 return 값으로 (c1, c2) -> {return int}형태의 람다식을 반환함을 알 수 있습니다.

그렇다면 comparingInt의 매개변수인 ToIntFunction은 어떤 모습이길래 Beverage::getPrice를 매개 변수로 받을 수 있는 것일까요?

comparingInt의 매개변수인 ToIntFuntion를 살펴보겠습니다.

@FunctionalInterface
public interface ToIntFunction<T> {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    int applyAsInt(T value);
}

ToIntFunction은 매개 변수 하나를 받아서 int를 반환하는 추상 메서드를 가지고 있는 함수형 인터페이스입니다.

따라서 예제 코드에 있는 comparingInt()는 매개변수의 형태에 맞는 메서드 레퍼런스인 Beverage::getPrice()를 인자로 받아서 (c1, c2) -> {return int}형태의 람다식을 반환합니다.
그리고 sorted()는 comparingInt가 반환해준 람다식을 인자로 하여 stream을 정렬해 주는 것을 확인할 수 있습니다.

List<Beverage> sortedBeverages = beverages.stream().sorted(Comparator.comparingInt(Beverage::getPrice))
            .collect(toList());

더 알아보고 싶은 것

  1. comparingInt가 람다식을 반환할 때 (Comparator & Serializable)는 왜 붙여주는 걸까?
  2. 직렬화
  3. Stream의 sorted 메서드는 어디에 구현되어있고, 내부적으로 어떻게 동작하는걸까?

좋은 웹페이지 즐겨찾기