Java

Lambda와 effectively final, capturing lambda

개발하고싶은개발자 2024. 12. 27. 16:38

람다를 사용할 때 아래와 같은 에러 문구를 보면서 궁금했던 점을 찾아보고 정리해보았다

Variable used in lambda expression should be final or effectively final

 

 

처음에는 단순하게 람다가 병렬로 처리되나? 라고 생각했는데 아니었다 ㅎㅎ

그래서 조사해본 결과 (effectively) final 이라는 제약이 왜 필요한지 언제 필요한지를 알게 되었다.

 

일단 effectively final이 무엇인지 정리해보면,

말 그대로 final 키워드는 없지만 초기화 된 이후 값이 한번도 변경되지 않았다면 effectively final이라고 한다

 

 

조사해보기 전에는 람다에서 외부 변수를 사용하려면 무조건 (effectively) final 변수이어야 하는 줄 알았는데 아니었다. 지역 변수, 인스턴스 변수, 클래스 변수 중에서 지역 변수를 사용할 때만 위와 같은 제약이 적용된다.

 

람다에서 외부 변수를 사용하는 경우를 capturing lambda, 사용하지 않는 경우를 non-capturing lambda라고 하는데 capturing lambda에 대해서만 정리하면 될 것 같다

 

 

 

capturing lambda

위에서 설명한대로 capturing lambda란 외부 변수를 사용하는 람다인데, 먼저 외부 변수 중 지역변수를 사용하는 람다에 대해서 알아보자.

 

 

외부 지역 변수를 사용하는 람다식

1. 일단 람다에서 외부 지역 변수를 사용할 때는 해당 변수를 복사해서 사용한다. 

2. 이렇게 사용되는 외부 지역변수는 (effectively) final 변수여야 하고 람다 내부에서 사용할 때도 final 변수로서 내부에서도 변경할 수 없다.

 

 

그럼 위의 제약이 왜 생기는지 알아보자

일단 람다도 객체이다. 그래서 해당 람다 객체는 Heap 영역에 생성되고, 람다의 메소드 즉, 람다 객체 내부 메소드는 다른 객체의 메소드처럼 메소드 영역에 존재한다. 

 

그런데 이 때 어떻게 외부 지역변수를 사용할 수 있을까?

외부 지역 변수는 람다식을 작성할 때의 스레드의 스택 영역에 있을 것이다. 따라서 람다를 바로 실행하지 않고 리턴할 수도 있기 때문에 같은 스레드에서 람다식을 실행하거나 같은 프레임에서 실행한다는 보장이 없다. 해당 지역 변수가 사라질 수 있다는 것이다.

따라서 람다는 이 외부 지역변수를 Heap에 생성된 람다 객체에 저장하고 사용한다.

이렇게 값을 복사해서 사용하기 때문에 외부 지역 변수의 값과 람다에서 갖고 있는 외부 지역 변수의 값은 동일함을 보장해야 한다. 하지만 위에서 설명한대로 외부 지역 변수는 스택영역에 존재하기 때문에 두 변수의 값의 동일함을 보장하기 위해서는 final이어야만 한다. 그러므로 외부 지역 변수도 final 이어야 하고, 람다가 갖고 있는 지역 변수도 final로 사용을 해야 한다.

 

 

지역 변수 이외의 다른 외부 변수 사용(클래스 변수, 인스턴스 변수)

클래스 변수, 인스턴스 변수등은 스택 영역이 아닌 힙, 메소드 영역에 존재하기 때문에 값의 동일함을 충분히 보장할 수 있기 때문에(volatile) 이런 제약 사항이 없이 사용할 수 있다.

 

 

 

참고 자료

https://vagabond95.me/posts/lambda-with-final/

https://lordofkangs.tistory.com/362