•
자바 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라는 함수가 있다고 하자.
▪
이제 다음처럼 f와 g를 조립해서 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
복사