한 마디로 더 깔끔하게 행동를 전달하는 것
같은 결과지만 다른 코드폼
Lambda Style
inventory.sort((Apple a1, Apple a2) -> a.getWeight().compareTo(a2.getWeight()));
Method Reference
inventory.sort(comparing(Apple::getWeight));
📗 메서드 참조를 새로운 기능이 아니라 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법이다.
몇가지 예제를 추가적으로 확인합니다.(MR = Method Reference)
lambda : (Apple apple) -> apple.getWeight();
MR : Apple:getWeight
lambda : () -> Thread.currentThread().dumpStack()
MR : Thread.currentThread()::dumpStack
lambda : (str, i) -> str.substring(i)
MR : sString::substring
lambda : (String s) -> System.out.prlintln(s) (String s) -> this.isValidName(s)
MR : this::isValidName
여러 종류의 메서드 참조
1. 정적 메서드의 경우 String.length()
, Integer.parseInt()
같은 것들이 존재
2. 인스턴스 메서드 (참조하고자 하는 메서드가 non static)
4. 생성자
public class Empty {
public Empty() {} // 기본 생성자
public Empty(Integer i) {} // 2번 생성자
public Empty(String s1, String s2, String s3) {} // 3번 생성자
}
1. Empty Class의 기본 생성자를 참조
Supplier<Empty> emptySupplier = Empty::new;
Empty empty = emptySupplier.get();
2. Empty Class의 2번 생성자 참조
Function<Integer, Empty> emptyFunc = Empty::new;
Empty empty2 = emptyFunc.apply(100);
3. Empty Class의 2번 생성자를 참조, 2번 생성자의 디스크립터와 일치한다.
List<Empty> empties = numbers.stream()
.map(Empty::new)
.collect(Collectors.toList());
4. 3번 생성자를 사용하기 위한 Functional Interface 정의
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
TriFunction<String, String, String, Empty> emptyTriFunc = Empty::new;
Empty empty1 = emptyTriFunc.apply("t1","u1","v1");
java 8 이전의 방식
직접 행동을 상속받은 클래스를 작성하며, 인스턴스를 생성하여 넘기는 방식입니다.
--- set up ---
사과 클래스
public class Apple {
public Color color;
public Apple(Color color) {
this.color = color;
}
public Color getColor() { return color; }
@Override
public String toString() {
return "Apple{" +
"color=" + color +
'}';
}
}
사과 Comparator 클래스
public class AppleComparator implements Comparator<Apple> {
@Override
public int compare(Apple o1, Apple o2) {
return o1.color.compareTo(o2.color);
}
}
--- execute ---
inventory.sort(new AppleComparator());
익명 클래스 사용
이전 방식과 차이는 Comparator 구현위치의 차이입니다.
해당 방식도 8 이전의 방식이며 가독성이 떨어집니다.
inventory.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.color.compareTo(o2.color);
}
});
람다 표현식 사용
람다 표현식을 사용하여 라인수가 크게 줄었습니다.
익명 클래스 보다는 람다 표현식이 훨씬 간결하여 추천합니다.
inventory.sort((Apple a1, Apple a2) -> a1.color.compareTo(a2.color));
메서드 참조 사용
람다 표현식 대신 사용할 수 있다면 좋은 선택지입니다.
더욱 간결해집니다.
inventory.sort(Comparator.comparing(Apple::getColor));
정적 메서드 Comparator.comparing()를 사용하여 키를 추출하는 Function기반 Comparator 반환
Comparator.comparing(Object::geState).reversed()
thenComparing() 을 체이닝을 통해 행위를 조합할 수 있습니다.
Comparator.comparing(Object::getState).reversed().thenComparing(Object::getState)
Predicate 조합
Predicate 인터페이스는 복잡한 프레디케이트를 만들 수 있도록 negate, and, or 3가지 method를 제공한다.
predicate의 조건을 반전
Predicate notRedApple = redApple.negate();
predicate에 새로운 predicate를 조합
Predicate heavyAndRedApple = redApple.and(apple -> apple.getWeight > 150);
Function 조합
Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공합니다.
andThen은 주어진 함수를 먼저 적용한 결과를 다른 합수의 입력으로 전달하는 함수를 반환한다.
수학으로 write(g(f(x)))로 표현
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);
compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외 부함수의 인수로 제공한다.
수학으로 f(g(x))로 표현
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);
유틸리티 메서드를 조합해서 다양한 변환 파이프라인을 만들어보겠습니다.
public static String addHeader(String text){ return "From Raoul, Mario and Alan" + text; }
public static String addFooter(String text){ return text + "From Raoul, Mario and Alan"; }
public static String checkSpelling(String text){ return text + "From Raoul, Mario and Alan"; }
Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader
.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
transformationPipeline.apply("hi, Java");