람다식(Lambda Expressions)에서의 지역 변수 사용 (JAVA)
안녕하세요? 코딩 중독입니다.
오늘은 람다식에서 지역 변수를 사용할 때 주의할 점에 대해서 자세히 알아 보겠습니다.
REVIEW
지난 람다식 포스팅에서는 개념과 함께 사용 방법을 익혔습니다. 그 중에서도 마지막 부분에 람다식에서 지역 변수를 사용하는 방법을 알아 보았는데, 메소드의 매개 변수나 지역 변수의 값을 바꾸면 안 된다고 하였습니다.
이번 포스팅을 이해하기 위해서는 JVM 메모리 구조를 알고 있어야 합니다. 완벽히 알 필요는 없고, 제가 저번 포스팅에서 적어 놓은 정도만 알아 두시면 됩니다.
Effectively Final
저번 람다 포스팅에서 매개 변수나 지역 변수의 값을 변경하면 아래와 같은 컴파일 에러가 발생하였습니다.
오류 메시지를 보면, 람다식에서 사용되는 변수는 final이거나 effectively final이어야한다고 되어있습니다. 그렇다면, final은 알겠는데, effectively final은 뭘까요? 쉽게 말하면, final은 아니지만 final의 속성을 가지고 있는 것을 말합니다. 즉, 값의 재할당이 일어나면 안 되는 것이죠.
람다 캡쳐링
람다식의 매개 변수가 아닌 외부에서 정의된 변수를 자유 변수라고 부르는데, 람다 바디에서 자유 변수를 참조하는 행위를 람다 캡쳐링이라고 합니다. 이때, 자유 변수가 인스턴스 변수거나 정적(static) 변수일 경우 캡쳐링을 원활하게 수행할 수 있습니다. 다만.. 지역 변수가 final이거나 effectively final일 경우 문제를 야기할 수 있습니다.
자유 변수가 final 속성이 아닌 지역 변수일 경우
자유 변수가 final 속성이 아닌 지역 변수일 경우 람다 캡쳐링을 수행할 때 문제가 발생하는데, 이것은 JVM 메모리 구조와 관련이 있습니다.
람다는 지역 변수가 존재하는 스택 영역에 직접 접근하는 것이 아니라 지역 변수를 자신(람다식이 동작하는 쓰레드)의 스택에 복사하여 사용합니다. 제가 저번 포스팅에서 지역 변수는 스택에 저장된다고 하였고, 각각의 쓰레드마다 고유의 스택을 갖고 있다고 설명하였습니다. 이러한 이유 때문에 지역 변수가 존재하는 쓰레드가 사라져도 람다는 복사된 값을 참조하면서 에러가 발생하지 않습니다.
만약, 우리가 멀티 쓰레드 환경에서 여러 개의 쓰레드가 동일한 람다식을 사용한다고 가정해 봅시다. 여러 개의 쓰레드가 람다식을 사용하면서 람다 캡쳐링을 진행하는데, 외부 변수가 변한다면 값이 계속해서 변하기때문에 동기(sync)화 문제가 발생하게 됩니다. 이러한 이유때문에 자유 변수로 지역 변수를 택할 경우, 그 변수는 final 속성을 지녀야 합니다.
그렇다면, 인스턴스 변수나 정적 변수를 자유 변수로 선택할 때는 어떨까요? 이 두 변수는 스택 영역이 아닌 힙 영역에 위치하게 되고, 람다식을 사용하는 쓰레드 측에서 두 변수는 항상 참조할 수 있습니다. 힙 영역은 모든 쓰레드가 공유하고 있는 메모리 영역이기때문이죠. 따라서, 자유 변수로 인스턴스 변수나 정적 변수를 자유 변수로 택할 경우, 그 변수는 final 속성을 가질 필요는 없습니다.
그리고 저번 포스팅의 주의사항에서도 강조한 것인데, 제가 말하는 메소드의 매개 변수는 람다식의 매개 변수가 아니라 람다식을 사용하는 메소드의 매개 변수를 말하는 것입니다. 그리고 이 메소드의 매개 변수도 지역 변수이므로 위에서 설명한 것과 동일하게 스택 영역에 저장이 됩니다.
정리
지금까지 람다식에서 자유 변수를 사용할 때 주의할 점에 대해서 알아 보았습니다. 그렇다면, 우리가 람다식 바디에서 지역 변수의 값을 변경할 방법은 없는 걸까요? 아닙니다. 람다식에 매개 변수를 넘겨주고, 그 변수를 람다식 바디에서 수정을 하고 리턴하는 식으로 코드를 짜면 됩니다.
여기서 MyFunctionalInterface 인터페이스에서 method는 매개 변수가 String 타입 하나이고, 리턴 타입도 String으로 정의하였습니다. 위와 같이 코드를 작성하면, name 변수는 "Yee"에서 "YeeSan"으로 변경되어, 출력 결과는 "YeeSan"이 될 것입니다.
그리고 여기서 한 가지 주의 사항이 더 있습니다. 이렇게 람다식을 정의할 때, 매개 변수는 이전에 사용한 변수의 이름과 달라야 합니다. 만약, mi = msg -> { } 형태가 아니라 mi = name -> { } 형태라면 컴파일 에러가 발생합니다.
이렇게 람다식은 코드가 단순해지는 장점이 있지만, 지역 변수를 사용할 때 몇 가지 조심해야하는 부분이 있습니다. 우리 모두 람다식 관련 컴파일 에러가 발생하지 않도록 연습을 많이 합시다!
참고 자료
'프로그래밍 언어 > Java' 카테고리의 다른 글
주요 함수적 인터페이스 (JAVA) (0) | 2021.01.04 |
---|---|
람다식(Lambda Expressions)의 메소드 참조 :: (JAVA) (1) | 2021.01.03 |
JVM 메모리 구조란? (JAVA) (1) | 2021.01.02 |
람다식(Lambda Expressions)의 개념과 사용 방법 (JAVA) (2) | 2021.01.01 |
enum이란? - 확장 (JAVA) (0) | 2020.12.18 |
댓글