Lambda Capturing

0

JAVA

목록 보기
9/18
post-thumbnail

람다 캡쳐링의 경우 저 역시 람다를 사용하기 막 시작했을 때 겪어 봤던 문제입니다.


간단한 예시를 작성해 보았습니다.




코드의 경우 lamdaTest 리스트에 1 ~ 5의 값을 넣은 뒤 값이 3보다 크면 var에 1을 할당 한 뒤 반환해주고 3보다 작으면 var의 원래 값인 0을 넣어주는 로직입니다.
로직 자체는 예시를 위해 만든 것이니 약간 불필요한 변수를 선언하거나 로직 자체가 별로여도 이해 부탁드립니다ㅎㅎ
그럼 의도한 결과 값은 [0, 0, 0, 1, 1]이 되게 됩니다.
하지만 코드를 보시면 람다 내부에 var에 경고줄이 그어져 있는 것을 확인할 수 있습니다.


경고문의 내용은 아래와 같습니다

Variable used in lambda expression should be final or effectively final


그럼 이 경고문의 의미는 무엇일까요?
해석하자면 다음과 같습니다.

람다 표현식 내부에 사용된 변수의 경우 final이거나 effectively final(초기화 된 이후 값이 한번도 변경되지 않음)이어야만 한다


왜 저런 경고문이 나오게 될까요?
결론부터 말씀드리면 아래와 같습니다.


지역 변수는 Stack 메모리 영역에 저장되기 때문에 람다식에서 값을 바로 참조하는 것에 제약이 존재한다.
따라서 값 복사본을 사용하는데, 이 때 멀티스레드 환경에서 복사 되거나 복사된 값이 변경 가능할 경우 동시성 이슈가 발생할 가능성이 존재하기 때문이다.





그럼 지금부터 결론의 의미를 확인해보도록 하겠습니다.

람다에서 변수 사용 예제


(1)의 경우 에러발생
(2), (3)의 경우 정상

Local Capturing lambda

외부 변수로 지역 변수를 이용하는 람다식을 의미합니다. 위 변수 사용 예제에서 (1)의 경우가 되겠습니다.
외부 변수로 지역 변수를 이용하는 람다식의 경우 몇가지 특징이 있습니다.

  • 람다식에서 사용되는 외부 지역 변수는 복사본이다
  • final, effectively final인 지역변수만 사용 가능
  • 복사된 지역 변수 값은 람다식 내부에서도 변경 불가능

위와 같은 제약조건이 생긴 이유의 근본적인 이유는 바로 지역변수의 경우 스레드마다 JVM의 스택영역에 존재하기 때문입니다. 관련해서 stackoverflow에 관련하여 좋은 답변이 있습니다. (https://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class)

요약하자면 다음과 같습니다.

익명 내부 클래스의 인스턴스를 생성할 때, 해당 익명 내부 클래스에서 사용된 어떠한 변수든 자동 생성된 생성자에 의해 각자의 복사본을 가지고 있다.
이러한 동작은 컴파일러가 옳바른 상태의 지역 변수를 바라보지 않고 자동생성 된 다양한 타입의 로직 내 논리성(상태가 존재하는?)이 존재하는 지역 변수를 바라 보는 것을 방지할 수 있기 때문이다.

익명내부클래스의 인스턴스 내부에 복사된 지역변수의 경우, 다른 메소드에 의해 값의 변경이나 수정이 발생하게 되면 컴파일러 입장에서는 적절한가에 대한 논리적 의문성이 발생 가능하다.
이런 경우에는 다른 시간(스레드와 같은 동작으로 이해)에 사용된 복사본을 가지고 동작했었기 때문에 구식(이미 사용이 완료된 혹은 스택 내에 더 이상 존재하는 스레드 내 지역변수로 이해함)이 된 변수를 가지고 동작하는 코드가 존재할 수 도 있다.

변수를 final 타입으로 변경하게 되면 이런 문제들을 제거할 수 있다.
즉, 값을 아에 변경되지 못하도록 한다면, 이런한 값 변경의 가시성에 대해 걱정하지 않아도 된다.



정리하자면 익명내부클래스와 람다 사용에 있어서 지역변수 사용을 final, effectively final로 사용하지 않으면 변수의 복사본을 참조하고 있는 입장에서 잘못된 참조를 할 수 있고 이로 인해 신뢰 가능한 변수 참조가 어려워지기 때문인 것으로 이해했습니다. 따라서, 람다에서 변수 사용 예제에서 (1)의 케이스는 에러가 발생하고 (2), (3)의 케이스는 jvm heap, mathod 메모리 영역에 존재하므로 각 스레드 마다 공유 가능한 자원이기에 문제가 발생하지 않는 것으로 생각됩니다.

지금까지 람다 캡처링에 대해 정리해보았습니다. 왜 안되는지는 간단하지만 그 내부 원리를 파악하고자 하면 생각보다 어려운 개념으로 저에게는 다가왔습니다. 람다 캐처링에 대해 이해하기 위해서는 익명내부클래스, 람다, JVM 내 변수 할당 등 복합적으로 알아야 할 개념들이 많기 때문입니다. 다시 한번 정리를 통해 저 역시 개념 복기하는 시간을 가질 수 있었습니다. 저 역시 영문을 보고 이해하였기에 틀린 내용이 존재할 수 있습니다. 혹여나 제가 작성한 내용에 잘못된 정보 혹은 논리상의 오류가 있다면 말씀해 주시면 감사하겠습니다.
profile
컴퓨터공학과 + 실무 = 4 + N = 모르는거 ∞ ...

0개의 댓글