Java Lambda 외부 지역 변수 final, effectively final

wannabeking·2023년 6월 4일
0

Java

목록 보기
13/13
post-thumbnail

Lambda에서 final 혹은 effectively final이 아닌 외부 지역 변수는 사용할 수 없습니다.

effectively final은 Java 8부터 람다식을 사용할 때 변수의 사용 범위와 관련된 문제를 해결하기 위해 도입
초기화된 후에 변경되지 않은 변수를 의미

위와 같이 인텔리제이는 람다식에 final 혹은 effectively final이 아닌 변수 사용에 대해 에러를 알려줍니다.


그렇다면 왜 Lambda에서 final이 아닌 외부 지역 변수를 사용할 수 없을까요?



Closure, Capturing

Lambda가 자신의 범위 밖의 변수를 사용하면, 그 것은 동시에 Closure가 됩니다.

Closure란 클래스 내에 정의한 변수를 Lambda가 직접 사용하는 것을 의미

Closure가 형성될 때 Closure가 참조하는 외부 지역 변수를 캡처해서 저장하는 과정을 가집니다.


캡처해서 저장하는 과정을 조금 더 알아보면,

public static void main(String[] args) {
	Supplier<Integer> supplier = generate();
    System.out.println(supplier.get()); // 1
}

private static Supplier generate<Integer>() {
	int i = 1;
    return () -> i;
}

위 코드의 경우 generate() 메소드 호출 시 스택 메모리에 i가 존재할텐데, 어떻게 메소드가 종료되어 스택 프레임이 소멸된 이후에도 i에 접근하여 출력할 수 있을까요?

사실 람다식은 i를 참조하고 있는 것이 아닌 i의 값을 현재 Lambda가 동작하고 있는 스레드의 스택에 캡처해서 저장하여 사용합니다.

이러한 과정을 Capturing이라 부릅니다.



final, effectively final

이러한 Closure, Capturing의 개념이 Lambda에서 final, effectively final인 외부 지역 변수만 사용 가능한 이유입니다.


멀티 스레드 환경에서 Lambda가 final이 아닌 외부 지역 변수를 Capturing하면, 다른 스레드에서 외부 지역 변수를 변경해도 람다식이 복사한 값에는 영향을 주지 않게됩니다.

따라서 항상 변수의 최신 값을 보장할 수 없기 때문에 final, effectively final이 아닌 외부 지역 변수 사용이 불가능한 것입니다.


하지만 final이 아닌 변수의 사용을 우회하는 방법이 있습니다.

외부 지역 변수를 원소 1개 크기의 배열로 감싸거나, Wrapper 클래스를 만들어 감싸고 해당 배열, 래퍼 클래스는 final로 만드는 것입니다.

배열과 래퍼 클래스의 값은 항상 최신임이 보장되지만 그 내부 필드의 값은 여러 스레드의 동시성 문제가 발생할 수 있습니다.

따라서 적절한 동기화 기법을 사용해야 합니다.


다시 정리하면, 외부 지역 변수를 final 또는 effectively final로 사용해야 하는 이유는 Lambda가 Closure를 생성하여 외부 지역 변수를 Capturing하는 동안, 변수 값의 일관성과 동시성 문제를 방지하기 위함입니다.



profile
내일은 개발왕 😎

0개의 댓글