1. 익명 클래스 vs 람다
람다는 익명 클래스의 대체 문법이지만 이 둘은 완전히 같지 않다!
익명 클래스
new Runnable() {
@Override
public void run() {
System.out.println(this);
}
};
- 새로운 클래스 생성
- this -> 익명 객체 자신
람다
Runnable r = () -> {
System.out.println(this);
};
- 클래스 생성 X
- this -> 외부 객체
차이
| 구분 | 익명 클래스 | 람다 |
| 생성 구조 | 클래스 생성 | 클래스 없음 |
| this | 자기 자신 | 외부 객체 |
| 바이트코드 | 클래스 파일 | invokedynamic |
| 목적 | 객체 생성 | 동작 전달 |
2. 타입 추론
람다식에 타입이 존재하지 않는다. 하지만 컴파일은 정상적으로 되는데 이는 함수형 인터페이스가 이미 타입을 알고 있기 때문이다.
Comparator<Integer> c = (a, b) -> a - b;
// 다음 순서로 해석
Comparator<Integer>
-> compare(Integer, Integer)
-> 반환 int
3. Function Descriptor
파라미터 타입과 반환 타입을 Function Descriptor라고 하는데 람다는 이 시그니처와 정확히 일치해야 한다.
4. effetively final
람다는 지역 변수를 직접 쓰는게 아니라 복사해서 쓴다.
int x = 10;
Runnable r = () -> {
System.out.println(x);
};
x++; // ❌ 컴파일 에러
x는 스택에 존재한다. 람다는 객체이기 때문이 heap에 존재한다. 하지만 람다는 나중에 실행될 수 있고, 직접 참조하면 지역 변수는 이미 사라지니 메모리 오류가 발생할 수 있다.
그래서 자바는 값 자체를 복사한다.
class LambdaObject {
final int x = 10; // 복사본
}
하지만 만약 값이 바뀐다면
int x = 10;
Runnable r = () -> System.out.println(x);
x = 20;
람다 내부는 x=10, 외부 변수는 x=20
두 값이 달라져 실행 시점마다 결과가 달라진다. 이로 인해 자바가 강제로 막아버리게 되었고, effectively final이 생기게 됨.
effectively final은 final은 아니지만 사실상 final 처럼 동작하는 변수이다! 값 변경 없는 변수만 람다에서 사용이 가능하다 !!
5. 람다와 Stream
stream.filter(x -> x > 10)
.map(x -> x * 2)
.forEach(System.out::println);
전부 함수형 인터페이스이다 !!
6. 정리
- 자바는 여전히 객체지향 언어
- 람다는 함수가 아닌 객체
- 동작을 값처럼 전달할 수 있게 됨