3.5 형식 검사, 형식 추론, 제약
3.5.1 형식 검사
람다가 사용되는 context를 이용해 람다의 형식을 추론할 수 있음
대상 형식: 어떤 context에서 기대되는 람다 표현식의 형식
List<Apple> heavierThan150g = filter(inventory, (Apple apple)->apple.getWeight() > 150);
형식 확인 과정
- filter 메서드의 선언을 확인한다.
- filter 메서드는 두 번째 파라미터로 Predicate<Apple> 형식을 기대한다. 여기서 Predicate<Apple>이 대상 형식이 된다.
- Predicate<Apple>은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스이다.
- test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
- filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.
3.5.2 같은 람다, 다른 함수형 인터페이스
호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있음
// 두 인터페이스 모두 제네릭 형식 T를 반환하는 함수를 정의하기 때문에
// 람다 표현식 ()->42이 두 식에 모두 사용가능하다.
Callable<Integer> c = ()->42;
PrivilegedAction<Integer> p = ()->42;
특별한 void 호환 규칙
람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환됨
// Pedicate는 불리언의 반환값을 가짐
Predicate<String> p = s->list.add(s);
// Consumer는 void 반환값을 가짐
Consumer<String> b = s->list.add(s);
3.5.3 형식 추론
자바 컴파일러는 람다 표현식이 사용된 context를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론함
대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론 가능함
컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략할 수 있음
// 파라미터의 형식이 명시적으로 지정되지 않았으므로 형식 추론 ㅇ
List<Apple> greenApples = filter(inventory, apple->GREEN.equals(apple.getColor()));
Comparator<Apple> c = (Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight()); // 형식 추론 X
Comparator<Apple> c = (a1, a2)->a1.getWeight().compareTo(a2.getWeight()); // 형식 추론 ㅇ
3.5.4 지역 변수 사용
람다 캡쳐링: 외부에서 정의된 변수(자유변수) 사용 가능
인스턴스 변수와 정적 변수를 자유롭게 캡쳐 가능
람다에서 사용되는 지역 변수는 final로 선언되거나, 실질적으로 final처럼 취급되어야 함
int portNumber = 1237;
Runnable r = ()->System.out.println(portNumber); // ERROR: 사용된 변수가 final 취급되지 않음
portNumber = 31337;
지역 변수의 제약
인스턴스 변수는 힙에 저장되고, 정적 변수는 스택에 저장됨
람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행되면, 변수를 할당한 스레드가 사라져서 변수 할당이 사라졌는데도 람다를 실행하는 스레드에서 해당 변수에 접근하려 할 수 있음
따라서 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공함. 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야한다는 제약이 생겼음
클로저
함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스
클로저는 클러저 외부에 정의된 변수의 값에 접근하고, 값을 바꿀 수 있음
Java8의 람다와 익명클래스는 클로저와 비슷한 동작을 수행함
3.6 메서드 참조
3.6.1 요약
기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있음
특정 메서드만을 호출하는 람다의 축약형
가독성을 높일 수 있음
// 기존 코드
inventory.sort((Apple a1, Apple a2)->a1.getWegiht().compareTo(a2.getWeight()));
// 메서드 참조 & java.util.Comparator.comparing
inventory.sort(comparing(Apple::getWeight)); //Apple 클래스에 정의된 getWeight의 메서드 참조
실제 메서드를 호출하는 것이 아님.
Apple::getWeight → (Apple a) -> a.getWeight()를 축약한 것
람다 | 메서드 참조 단축 표현 |
(Apple apple)->apple.getWeight() | Apple::getWeight |
()->Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i)->str.substring(i) | String::substring |
(String s)->System.out.println(s) | System.out::println |
(String s)->this.isValidName(s) | this::isValidName |
메서드 참조를 만드는 방법
ClassName::MethodName
- 1) 정적 메서드 참조
- ex) Integer::parseInt
- 2) 다양한 형식의 인스턴스 메서드 참조
- ex) String::length
- 3) 기존 객체의 인스턴스 메서드 참조
- ex) Transaction 객체를 할당받은 expensiveTransaction 지역 변수가 있고, Transaction 객체에는 getValue 메서드가 있다면, 이를 expensiveTransaction::getValue라고 표현할 수 있음
3.6.2 생성자 참조
ClassName::new
클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있음
만들어진 생성자의 참조는 이와 일치하는 시그니처를 갖는 함수형 인터페이스 부분에서 사용됨
// 두 식은 같음
Supplier<Apple> c1 = Apple::new;
Supplier<Apple> c1 = ()->new Apple();
List<Integer> weights = Arrays.asList(7,3,4,10);
List<Apple> apples = map(weight, 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<String, Function<Integer, Fruit>> map = new HashMap<>();
static{
map.put("Apple", Apple::new);
map.put("Orange", Orange::new);
// 등등
}
public static Fruit giveMeFruit(String fruit, Integer weight){
return map.get(fruit.toLowerCase()).apply(weight);
}
3.7 람다, 메서드 참조 활용하기
문제: 사과 리스트를 정렬
1단계) 코드 전달
public class AppleComparator implements Comparator<Apple>{
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
2단계) 익명 클래스
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()));
import static java.util.Comparator.comparing;
inventory.sort(comparing(apple -> apple.getWeight()));
4단계) 메서드 참조 사용
inventory.sort(comparing(Apple::getWeight));
3.8 람다 표현식을 조합할 수 있는 유용한 메서드
여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수 있음
이를 위해 함수형 인터페이스에서 디폴트 메서드를 제공함
디폴트 메서드는 추상 메서드가 아니라서 함수형 인터페이스 정의에서 벗어나지 않음
3.8.1 Comparator 조합
역정렬
inventory.sort(comparing(Apple::getWeight).reversed());
Comparator 연결
무게가 같은 두 사과가 존재한다면 어떤 사과를 먼저 나열할까?
이를 위해 thenComparing 메서드를 사용해서 Comparator를 연결할 수 있음
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));
3.8.2 Predicate 조합
Predicate 인터페이스에서는 negate, and, or 세가지 메서드를 제공함
// 빨간색이 아닌 사과
Predicate<Apple> notRedApple = redApple.negate();
// 빨간색이면서 무거운 사과
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
// 빨간색이면서 무거운 사과이거나 녹색 사과
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple->apple.getWeight() > 150).or(apple -> GREEN.equals(apple.getColor()));
3.8.3 Function 조합
- Function 인터페이스에서는 andThen, compose 디폴트 메서드를 제공함
- andThen: 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환
- ex) f.andThen(g) → g(f(x))
- compose: 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공함
- ex) f.compose(g) → f(g(x))
Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter:checkSpelling).andThen(Letter::addFooter);
'백엔드 > 모던자바인액션' 카테고리의 다른 글
ch15. CompletableFuture와 리액티브 프로그래밍 컨셉의 기초 (0) | 2023.03.23 |
---|---|
[ch4] 스트림 소개 (0) | 2022.09.19 |
[ch3] 람다 표현식 (1) (0) | 2022.09.02 |
[ch2] 동작 파라미터화 코드 전달하기 (0) | 2022.09.01 |
ch1. 자바 8, 9, 10, 11 무슨 일이 일어나고 있는가? (0) | 2022.07.23 |
댓글