참새의 이야기
Chapter 3. 람다 표현식 본문
앞선 챕터에서 등장한 람다 표현식을 조금 더 자세히 알아보고 메서드 참조에 대해서도 알아보자.
람다는 무엇인가
- 람다는 이름이 없는 익명이다.
- 람다는 특정 클래스에 종속되지 않으므로 함수다.
- 메서드의 파라미터로 전달하거나 변수로 저장할 수 있다.
람다 표현식
(Apple a1, Apple a2) → a1.getWeight().compareTo(a2.getWeight());
람다는 세 부분으로 이루어진다.
- (Apple a1, Apple a2) 파라미터 리스트
- → 화살표
- a1.getWeight().compareTo(a2.getWeight()); 람다 바디
어디에서 쓸 수 있는가
람다 표현식은 함수형 인터페이스라는 문맥에서 사용할 수 있다.
함수형 인터페이스란 추상 메서드가 오직 하나인 인터페이스를 말한다.
예를 들어 아래의 Adder는 함수형 인터페이스다.
public interface Adder {
int add(int a, int b);
}
하지만 SmartAdder의 경우에는 함수형 인터페이스가 아니다.
public interface SmartAdder extends Adder {
int add(double a, double b);
}
눈에 보이기로는 하나지만, Adder에서 또 다른 하나를 상속받기 때문이다.
함수형 인터페이스임을 보장하고 싶다면 @FunctionalInterface
를 사용할 수 있다.
이 annotation이 붙어있는데 함수형 인터페이스가 아니라면 컴파일 시점에서 에러가 발생한다.
함수 디스크립터
람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터라고 한다.
예를 들어 함수형 인터페이스 중 Predicate는 어떤 타입을 받아 boolean을 반환한다.
따라서, Predicate의 함수 디스크립터는 T → boolean
이다.
람다의 실제 형식
람다 표현식에는 어떤 인터페이스를 구현하는지의 정보가 포함되어 있지 않다.
람다 표현식을 제대로 이해하기 위해 람다의 실제 형식을 파악해보자.
형식 검사
람다의 사용 맥락(context)에 따라 람다의 형식을 추론할 수 있다.
기대되는 타입을 대상 형식이라고 한다.
예를 들어, 아래의 상황에서 두 번째 파라미터는 Predicate이 대상 형식이다.
List<Apple> heavierThan150g =
filter(inventory, (Apple apple) -> apple.getWeight() > 150);
이 코드의 형식 파악 과정을 살펴보자면, 우선 filter의 선언을 확인한다.
filter(List<Apple> inventory, Predicate<T> p);
를 통해 두 번째 파라미터가 Predicate이어야 함을 파악했다면, Predicate의 test 메서드가 boolean을 반환해주기를 기대할 수 있다.
위의 코드는 boolean을 반환하므로 유효한 코드가 된다.
같은 람다 표현이지만 다른 함수형 인터페이스
대상 형식이라는 특징 때문에 같은 람다 표현이라도 아래처럼 다른 함수형 인터페이스로 사용될 수 있다.
Callable<Integer> c = () -> 1;
PrivilegedAction<Integer> p = () -> 1;
지역 변수 사용
람다 표현식에서는 외부에서 정의된 변수도 사용할 수 있다.
주의해야 할 점은 사용되는 변수는 final로 선언되거나 변하지 않아야 한다.
람다가 지역 변수에 바로 접근하도록 한다면 변수 할당이 해제되었을 경우 문제가 생긴다.
그렇기 때문에 지역 변수에 접근할 때는 복사본에 접근하도록 하므로 변수의 값이 바뀌지 않아야 하는 것이다.
메서드 참조
메서드 참조는 코드의 가독성을 획기적으로 개선할 수 있는 방법이다.
(Apple apple) → apple.getColor()
과 같은 코드가 있다면 이를 Apple::getColor
로 개선할 수 있다.
어떤 클래스에 정의된 메서드를 간결하게 호출하는 것이라고 생각하면 좋다.
ToIntFunction<String> stringToInt = (String s) -> Integer.parseInt(s);
BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);
Predicate<String> startsWithNumber = (String s) -> this.startsWithNumber(string);
위의 람다 표현식을 메서드 참조로 바꿔보면 아래와 같다.
Function<String, Integer> stringToInt = Integer::parseInt;
BiPredicate<List<String>, String> contains = List::contains;
Predicate<String> startsWithNumber = this::startsWithNumber;
생성자 참조
BiFunction<String, Integer, Apple> b = (color, weight) -> new Apple(color, weight);
Apple apple = b.apply(GREEN, 120);
람다 표현식으로 생성자를 호출하던 것을 메서드 참조를 이용하면 아래의 코드처럼 간단히 정리할 수 있다.
'JAVA > 모던 자바 인 액션' 카테고리의 다른 글
Chapter 5. 스트림 활용 (1) | 2023.11.18 |
---|---|
Chapter 4. 스트림 소개 (1) | 2023.11.16 |
Chapter2. 동작 파라미터화 (2) | 2023.11.14 |
Chapter 1. 자바의 변화 (0) | 2023.11.14 |