형식 검사, 형식 추론, 제약
람다로 함수형 인터페이스의 인스턴스를 만들 수 있다고했다.
람다 표현식 자체에는 람다가 어떤 함수현 인터페이스를 구현하는지 정보가 없다.
따라서 제대로 람다표현식을 이해하려면 람다의 실제 형식을 파악해야 한다.
형식검사
람다가 사용되는 콘텍스트를 이용해 형식을 추론 가능.
어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식 target type 이라고 한다.
List<Apple> heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
- filter 메서드의 선언을 확인한다.
- filter 메서드는 두번째 파라미터로 Pradicate<Apple> 형식(대상형식)을 기대한다.
- Pradicate<Apple>은 test 라는 한 개의 추상메서드를 정의하는 함수형 인터페이스다.
- test메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
- filter메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.
같은 람다, 다른 함수형 인터페이스
대상 형식이라는 특징 때문에 같은 람다표현식이라도 다른 함수형 인터페이스 추상메서드와 호환 사용가능.
Comparator<Apple> c1 =
(Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight());
ToIntBiFunction<Apple, Apple> c2 =
(Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight());
BiFunction<Apple, Apple, Integer> c3 =
(Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight());
* 람다의바디에 일반표현식이 있으면 void를 반환하는 함수 디스크립터와 호환.
Consumer 콘텍스트 (T->void)가 기대하는 void 대신 boolean 를 반환하더라도 유효함. *
형식 추론
컴파일러는 람다 표현식의 파라미터에 접근할 수 있으므로 람다문법에서 파라미터 형식을 생략할 수 있다.
Comparator<Apple> c =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c =
(a1,a2) -> a1.getWeight().compareTo(a2.getWeight()); // 형식 추론
지역변수 사용
람다 표현식에서 익명함수 처럼 자유변수(파라미터로 넘겨인 변수가 아닌 외부 정의된 변수)를 활용 가능하다.
하지만 지역변수는 명시적으로 final로 선언되어 있어야 하거나 final처럼 취급되어야 함.
외부변수를 변화시키는데 제동을 건다. (병렬화를 위해)
메서드 참조
메서드 참조를 이용해 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.
특정 메서드만을 호출하는 람다의 축약형
명시적으로 메서드명을 참조함으로써 가독성을 높일 수 있다.
람다 |
메서드 참조 단축 표현 |
(Apple apple)-> apple.getWeight() | Apple::getWeight |
()->Thread.currentTread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i)-> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
(String s) -> this.isVaildName(s) | this::isValidName |
메서드 참조를 만드는 방법
정적 메서드 참조 |
(args)->ClassName.staticMethod(args) | ClassName::staticMethod |
다양한 형식의 인스턴스 메서드 참조 |
(arg0, rest)->arg0.instanceMethod(rest) | ClassName::instanceMethod |
기존 객체의 인스턴스 메서드 참조 |
(args)->expr.instanceMethod(args) | expr::instanceMethod |
컴파일러는 메서드 참조가 주어진 함수형 인터페이스와 호환하는지 확인한다.
즉, 메서드 참조는 콘텍스트의 형식과 일치해야한다.
생성자 참조
ClassName::new 처럼 클래스명과 new 키워드를 이용해 기존 생성자의 참조를 만들수 있음.
정적 메서드 참조 만드는 것과 비슷.
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get(); // get메서드 호출해서 새로운 Apple객체를 만들수 있다.
//람다식
Function<Integer, Apple> c2 = (weight)-> new Apple(weight);
//생성자 참조
Function<Integer, Apple> c2 = Apple::new;
//Function의 apple메서드에 무게를 인수로 호출해서 새로운 Apple 객체 만듬
Apple a2 = c2.apply(110);
다양한 무게를 포함하는 사과 리스트 만들기
List<Integer> weights = Arrays.asList(7,3,4,10);
List<Apple> apples = map(weights, Apple::new);
public List<Apple> map(List<Integer> list, Function<Integer, Apple> f) {
List<Apple> result = new ArrayList<>();
for(Integer i:list) {
result.add(f.apply(i));
}
return result;
}
인스턴스화 하지않고도 생성자에 접근할 수 있는 기능을 다양한 상황에 응용할 수 있음.
static Map<Sting, Function<Integer, Fruit>> map = new HashMap<>();
static {
map.put("apple", Apple::new);
map.put("orange", Orange::new);
}
public static Fruit giveMeFruit(String fruit, Integer) {
return map.get(fruit.toLowerCase()) //map에서 Function<Integer, Fruit> 얻음.
.apply(weight); // Function의 apply 메서드에 정수 무게 파라미터를 제공해서 Fruit만듬.
}
람다, 메서드 참조 활용하기
사과 리스트를 다양한 정렬 기법으로 정렬하는 문제
최종목표는 다음과 같은 코드를 만드는 것
inventory.sort(comparing(Apple::getWeight));
1단계 : 코드전달
List의 sort 메서드는 다음과 같은 시그니처를 갖는다.
void sort(Comparator<? super E>)
public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
2단계 : 익명클래스 사용
한번만 사용할 Comparator 이기에 익명클래스로 구현
inventory.sort(new Comparator<Apple>(){
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
});
3단계 : 람다 표현식 사용
inventory.sort((Apple a1, Apple a2)->
a1.getWeight().compareTo(a2.getWeight())
);
//컴파일러 람다 파라미터 형식 추론
inventory.sort((a1, a2)-> a1.getWeight().compareTo(a2.getWeight());
// Comparator는 Comparable 키를 추출해 Comparator 객체로 만드는
// Function 함수를 인수로 받는 정적 메서드 comparing 메서드를 사용 가능.
Comparator<Apple> c = Comparator.comparing((Apple a)-> a.getWeight());
import static java.util.Comparator.comparing;
inventory.sort(comparing(apple-> apple.getWeight()));
4단계 : 메서드 참조 사용
inventory.sort(comparing(Apple::getWeight));
코드가 간결할 뿐만 아니라 그 자체로 'Apple을 weight별로 비교해서 inventory를 sort하라' 는 의미를 전달 할 수 있다.
람다 표현식을 조합할 수 있는 유용한 메서드
함수형 인터페이스는 람다 표현식을 조합할 수 있도록 유틸리티 메서드를 제공한다.
여러개의 람다 표현식을 조합해서 복잡한 람다표현식을 만들 수 있다.
여기서 등장하는 것이 디폴트 메서드 default method
Comparator 조합
역정렬
사과의 무게를 내림차순으로 정렬하고 싶다면 ?
주어진 비교자의 순서를 바꾸는 reverse 디폴트 메서드 이용.
inventory.sort(comparing(Apple::getWeight).reversed());
comperator 연결
무게가 같은경우엔? thenComparing 메서드로 두번째 comperator 객체를 연결시켜줌.
inventory.sort(comparing(Apple::getWeight)
.reversed()
. thenComparing(Apple::getCountry));
Pradicate 조합
negate, and, or 세가지 메서드 제공
특정 프레디케이트 반전시킬때 - negate
두 람다를 조합 - and, or
Function 조합
Function 인스턴스를 반환하는 andThen, compose 디폴트 메서드 제공
여러 유틸리티 메서드를 조합해서 다양한 변환 파이프라인을 만들 수 있음.
비슷한 수학적 개념
적분
f(x) = x + 10 함수가 차지하는 영역인 적분함수를 구현해보자.
f와 한계값을 인수로 받는 integrate 함수를 만들자
수학에서 dx의 정체는 'x를 인수로 받아 x+10의 결과를 만드는 함수'
자바8 람다로 연결
integrate((double x) -> f(x), 3, 7);
함수형 인터페이스를 기대하는 콘텐스트에서만 람다 표현식을 사용할 수 있으므로 다음처럼 구현
public double intergrate(DoubleFunction<Double> f, double a, double b) {
return (f.apply(a) + f.apply(b)) * (b-a) / 2.0;
}
또는 DoubleUnaryOperator를 이용해도 결과를 박싱할 필요없다
public double intergrate(DoubleUnaryOperator f, double a, double b) {
return (f.applyAsDouble(a) + f.applyAsDouble(b)) * (b-a) / 2.0;
}
- 람다 표현식은 익명함수의 일종이다. 이름은 없지만 파라미터 리스트, 바디, 반환 형식을 가지며 예외를 던질 수 있다.
- 간결한 코드를 구현할 수 있다.
- 함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스다.
- 함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다.
- 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다.
- java.util.function 패키지는 Pradicate<T>, Function<T,R.>, Supplier<T>, Consumer<T> 등 다양한 함수형 인터페이스 제공.
- 제네릭 함수형 인터페이스와 관련한 박싱동작을 피할 수 있는 IntPredicate, IntToLongFunction등 기본형 특화 인터페이스도 제공.
- 실행 어라운드 패턴을 람다와 활용하면 유연성과 재사용성을 얻을수 있다.
- 람다 표현식의 기대형식을 대상 형식 이라고 한다.
- 메서드 참조를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있다.
- Comparator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공.
'IT Book Summary > ModernJavaInAction' 카테고리의 다른 글
Chapter5 스트림 활용 (0) | 2020.03.11 |
---|---|
Part 2 함수형 데이터 처리 - Chapter 4 스트림 소개 (3) | 2020.03.02 |
Chapter3 람다 표현식 _ 01 (0) | 2020.02.27 |
Chapter 2 동작 파라미터화 코드 전달하기 (0) | 2020.02.23 |
Part 1 기초 - Chapter 1 자바8, 9, 10, 11 (0) | 2020.02.23 |