728x90
람다에서 사용하는 외부 지역 변수가 final 혹은 effectively final이어야 하는 이유
effectively final의 의미 변수가 초기화된 이후 값이 한 번도 변경되지 않았다면 effectively final이라 할 수 있다.
자바 8 이전에는 final 키워드를 반드시 붙여야만 했으나 자바8부터는 이러한 변수를 final로 간주할 수 있게 되었다.
람다에서 외부 지역 변수가 final 혹은 effectively final이어야 하는 이유는 람다 표현식 내부에서 사용하는 외부 변수의 동작 방식과 관련이 있어.
람다는 익명 내부 클래스(Anonymous Inner Class)처럼 클로저(Closure) 형태로 동작해. 즉, 람다 내부에서 외부 지역 변수를 참조할 때, 해당 변수의 복사본이 생성되어 사용돼. 이 복사본은 람다 표현식이 실행될 때까지 유지되어야 하고, 변경되지 않아야 일관성을 유지할 수 있어. 만약 외부 변수를 변경할 수 있다면, 복사본과 실제 변수 값이 달라지면서 예측 불가능한 동작이 발생할 수 있어.
정리하면:
- 일관성 보장: 람다 내부에서 사용된 변수는 실행 시점까지 동일한 값을 유지해야 함.
- 클로저 구현: 외부 변수의 복사본을 참조하기 때문에 변경할 수 없음.
- 컴파일러 요구 사항: 자바 컴파일러는 외부 변수가 변경될 가능성이 있으면 에러를 발생시켜 안전한 동작을 보장함.
public static void main(String[] args) {
int num = 10; // effectively final
Runnable r = () -> System.out.println(num);
r.run(); // 출력: 10
// num++; // 이렇게 값을 변경하려고 하면 컴파일 에러 발생
}
num이 변경될 수 없는 상태이기 때문에 람다 내부에서 안전하게 참조할 수 있어.
effectively final은 명시적으로 final 키워드를 붙이지 않았지만, 값이 한 번만 할당되어 변경되지 않는 변수를 의미해. num처럼 값을 한 번만 할당하고 변경하지 않는 경우가 이에 해당하지.
public static void main(String[] args) {
int num = 10; // effectively final이 아님
Runnable r = () -> System.out.println(num); // 컴파일 에러 발생!
num++; // num 값을 변경하려고 함.
r.run();
}
왜 컴파일 에러가 발생하나?
- num이 effectively final이 아님:
num++으로 값을 변경했기 때문에 더 이상 effectively final이 아니다. - 람다 내에서 외부 변수의 복사본을 사용하는 구조:
람다 내부에서는 num의 복사본을 사용하려고 하는데, 외부에서 값이 변경되면 어떤 값을 참조해야 할지 모호해지기 때문에 에러가 발생한다.
해결 방법:
1.변수를 final로 선언하거나 값을 변경하지 않도록 하면 된다.
public static void main(String[] args) {
final int num = 10; // final로 선언하여 변경 불가능하게 함
Runnable r = () -> System.out.println(num);
r.run(); // 출력: 10
}
2. 값을 변경해야 하는 경우 배열이나 객체를 사용
public static void main(String[] args) {
int[] num = {10}; // 배열을 사용하면 변경 가능
Runnable r = () -> System.out.println(num[0]);
num[0]++; // 배열 요소 값을 변경
r.run(); // 출력: 11
}
이 경우 배열의 참조는 변경되지 않기 때문에 effectively final 조건을 만족하고, 내부 값은 자유롭게 변경할 수 있어.
728x90