/////
Search
Duplicate
8️⃣

람다 표현식을 조합할 수 있는 유용한 메서드

자바 8 API의 몇몇 함수형 인터페이스는 다양한 유틸리티 메서드를 포함한다.
예를 들어 Comparator, Function, Predicate 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있도록 유틸리티 메서드를 제공한다.
이것은 무슨 의미일까? 간단히 말해 간단한 여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수 있다는 것이다.
두 프레디케이트를 조합해서 두 프레디케이트의 or 연산을 수행하는 커다란 프레디케이트를 만들 수 있다.
한 함수의 결과가 다른 함수의 입력이 되도록 두 함수 조합할 수도 있다.
도대체 함수형 인터페이스에서는 어떤 메서드를 제공하기에 이런 일이 가능할까? 여기서 등장하는 것이 바로 디폴트 메서드다.

1. Comparator 조합

이전에도 보았듯이 정적 메서드 Comparator.comparing을 이용해서 비교에 사용할 키를 추출하는 Fucntion 기반의 Comparator를 반환할 수 있다.
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
Java
복사
역정렬
사과의 무게를 내림차순으로 정렬하고 싶다면 새로운 Comparator 인스턴스를 정의해야할까?
그럴 필요가 없다. 인터페이스가 제공하는 비교자의 순서를 뒤바꾸는 reverse를 사용하면 된다. 따라서 다음코드처럼 처음 비교자 구현을 재사용하면 된다.
inventory.sort(comparing(Apple::getWeight).reversed());
Java
복사
Comparator 연결
정렬이 잘 동작하는 것 같다. 하지만 무게가 같은 두 사과가 존재한다면 어떻게 해야 할까? 정렬된 리스트에서 어떤 사과를 먼저 나열해야할까?
비교 결과를 더 다듬을 수 있는 두 번째 Comparator를 만들 수 있다.
예를 들어 무게로 두 사과를 비교한 다음에 무게가 같다면 원산지 국가별로 사과를 정렬할 수 있다.
thenComparing 메서드로 두 번째 비교자를 만들 수 있다.
thenComparing은 함수를 인수로 받아 첫 번째 비교자를 이용해서 두 객체가 같다고 판단되면 두 번째 비교자에 객체를 전달한다.
이를 코드로 구현하면 다음과 같이 구현된다.
inventory.sort(comparing(Apple::getWeight)) .reversed() .thenComparing(Apple::getCountry));
Java
복사

2. Predicate 조합

Predicate 인터페이스는 복잡한 프레디케이트를 만들 수 있도록 negate, and, or 세 가지 메서드를 제공한다.
negate 메서드를 사용하면 빨간색이 아닌 사과처럼 특정 프레디케이트를 반전시킬 수 있다.
Predicate<Apple> notRedApple = redApple.negate() // 기존 프레디케이트 객체 redApple의 결과를 반전시킨 객체를 만든다.
Java
복사
and 메서드를 이용해서 빨간색이면 무거운 사과를 선택하도록 두 람다를 조합할 수 있다.
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
Java
복사
or 메서드를 이용하면 빨간색이면서(150그램 이상) 사과 또는 그냥 녹색 사과와 같은 다양한 조건을 만들 수 있다.
Predicate<Apple> redAndHeavyAppleOrGreaen = redApple.and(apple -> apple.getWeight() > 150) .or(apple -> GREEN.equals(a.getColor()));
Java
복사
단순한 람다 표현식을 조합해서 더 복잡한 람다 표현식을 만들 수 있게 되었다.
심지어 람다 표현식을 조합해도 코드 자체가 비즈니스 로직을 잘 표현하고 있다.
여기서 소개한 and, or 등은 왼쪽에서 오른쪽으로 연결되었는데, 이는 a.or(b).and(c)로 개략화할 수 있고 (a || b) && c와 같다.

3. Function 조합

마지막으로 Function 인터페이스에서 제공하는 람다 표현식도 조합할 수 있다.
Function 인터페이스는 Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공한다.
andThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환한다.
예를 들어 (x → x + 1)시키는 f라는 함수가 있고 숫자에 2를 곱하는 g라는 함수가 있다고 하자.
이제 다음처럼 fg를 조립해서 1을 더하고 다시 2를 곱하는 h라는 함수를 만들 수 있다.
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = x -> f.andThen(g); // 수학적으로는 g(f(x)), (g o f)(x) int result = h.apply(1); // 4를 반환
Java
복사
compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공한다.
f.andThen(g)에서 andThen 대신에 compose를 사용하면 g(f(x))가 아니라 f(g(x))가 된다.
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = x -> f.compose(g); // 수학적으로는 f(g(x)), (f o g)(x) int result = h.apply(1); // 3을 반환
Java
복사
한 번 더 예시를 살펴보자.
다음처럼 문자열로 구성된 편지 내용을 변환하는 다양한 유틸리티 메서드가 있다고 가정하자.
public class Letter { public static String addHeader(String text) { return "From Raoul, Mario and Alan: " + text; } public static String addFooter(String text) { return text + " Kind regards"; } public static String checkSpelling(String text) { return text.replaceAll("labda", "lambda"); } }
Java
복사
이런 유틸리티 메서드를 조합해서 다양한 변환 파이프라인을 만들 수 있다.
Function<String, String> addHeader = Letter::addHeader; Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling) .andThen(Letter::addFooter);
Java
복사