Java & Spring/스트림

람다 표현식 2

승주우에요 2026. 1. 19. 08:48

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. 정리

  • 자바는 여전히 객체지향 언어
  • 람다는 함수가 아닌 객체
  • 동작을 값처럼 전달할 수 있게 됨