사실상 final(effective final)이란?

2023. 6. 6. 20:46High Level Programming Language/Learning the Java Language

사실상 final인 바깥쪽 블록의 로컬 변수 및 파라미터”란 자바 람다와 익명 클래스에서 매우 중요한 개념입니다.
이 개념을 완전히 이해하려면 ‘final’과 ‘effectively final(사실상 final)’의 차이부터 설명드리는 게 핵심입니다.

🧠 사실상 final(effective final)이란?

✅ 정의

사실상 final(effective final)이란,
"코드에서 단 한 번만 값이 할당되고, 이후 변경되지 않는 지역 변수나 파라미터를 말합니다."
즉, 명시적으로 final  키워드를 붙이지 않아도 실제로는 변경되지 않는  변수입니다.

🔍 예제 1 – final사실상 final 변수

public void test() {
    final int a = 10;          // 명시적으로 final
    int b = 20;                // 사실상 final (값을 변경하지 않음)
    int c = 30;

    c = 40;                    // c는 더 이상 사실상 final이 아님

    Runnable r = () -> {
        System.out.println(a); // OK
        System.out.println(b); // OK (사실상 final)
        // System.out.println(c); // ❌ 컴파일 오류 - c는 변경되었음
    };

    r.run();
}

🔎 위 코드에서 분석

변수 값 변경 여부 final 여부 사용 가능 여부
a 변경 없음 final ✅ 가능
b 변경 없음 (없음) ✅ 가능 (사실상 final)
c 변경함 (없음) ❌ 불가능

📌 왜 이런 제약이 있는가?

람다나 익명 클래스는 외부 메서드의 지역 변수를 참조할 수 있지만,
그 변수는 스택 프레임에 저장되고 메서드 종료 시 사라집니다.

그래서 자바는 내부적으로 복사본을 만들어 저장하는 방식을 사용합니다.
그런데 이 복사본이 실제 변수와 다르게 변경된다면 버그와 불일치가 발생할 수 있기 때문에,
"람다에서 접근하는 외부 변수는 반드시 변하지 않아야 한다"는 제약을 둡니다.

즉, “람다에서 외부 변수를 참조할 수는 있지만, 절대 변경하면 안 된다”는 안전장치입니다.

📎 참고: 파라미터도 동일한 규칙 적용

public void printMessage(String message) {
    // message는 사실상 final이므로 람다에서 접근 가능
    Runnable r = () -> System.out.println(message);
    r.run();
}
  • 여기서 message는 메서드 파라미터입니다.
  • 이 파라미터를 다른 값으로 대입하지 않았기 때문에, 사실상 final로 간주됩니다.
  • 따라서 람다에서 접근 가능합니다.

🚫 변경 시 예외

public void printMessage(String message) {
    message = "new message"; // 값 변경함 → 더 이상 사실상 final 아님

    Runnable r = () -> System.out.println(message); // ❌ 컴파일 오류 발생
}

🧾 정리

용어 의미
final 변수 명시적으로 변경 불가
사실상 final 코드 상으로 변경하지 않았지만, final 키워드는 안 붙은 변수
람다에서 접근 가능한 외부 변수 final 또는 사실상 final지역 변수/파라미터만 가능
변경된 변수 ❌ 접근 불가 (컴파일 오류)

💬 결론

자바에서 람다 표현식은 외부 로컬 변수나 파라미터에 접근할 수 있지만,해당 변수는 반드시 final이거나 사실상 final이어야 합니다.
이 제약은 람다 내부에서 변수 복사본을 안전하게 유지하고, 사이드 이펙트를 방지하기 위한 설계입니다.