베하!
안녕하세요 여러분, 일단고입니다.
오늘은 개발하면서 알게된 ‘람다 캡처링’에 대해서 알아보겠습니다.
얼마전 개발을 진행하다 람다식을 사용했는데요. 그때 Variable used in lambda expression should be final or effectively final 오류를 만났습니다. 람다 캡처링이 발생했기 때문인데요. 그래서 람다 캡처링이 무엇인지에 대해 알아보겠습니다.
발생한 오류
Variable used in lambda expression should be final or effectively final 라는 오류로 제가 사용하는 intellij가 알려주고 있습니다.
이 에러를 직역해 보면 람다 표현식에서는 final effectively final 변수만 사용할 수 있다는 말로 보이는데요.
final은 아시다시피 상수로써 변하지 않는 값을 말하고 람다 표현식은 그 값들만 참조할 수 있다는 말입니다. 먼저 예제 코드를 보겠습니다.
static void lambdaTest(){
int flushCount = 0;
List<Integer> intList = Arrays.asList(10,20,30,40,50,60,70,80,90,100);
intList.stream().forEach(integer -> {
if(flushCount > 5){
intList.clear();
}
flushCount ++;
System.out.println(flushCount);
});
}
위 코드에서 flushCount 변수를 람다식 밖같에 위치 시키고 ++를 통해 값을 하나씩 증가 시키면 오류가 발생합니다.
하지만 여기서 flushCount의 값을 변경시키지 않고 읽기만 한다면 문제가 없습니다.
intellij 가 오류를 보여주지 않습니다. 물론 오류메시지 대로 final을 쓰면 좋겠지만 제가 말하고자 하는 것은
여기서 말하고자 하는 것은 외부 변수의 값을 변경할 수 없고 읽기만 해야한다는 것을 알 수 있습니다.
그럼 컬랙션은 어떨까요?
static void lambdaTest(){
int flushCount [] = new int [1];
flushCount[0] = 0;
List<Integer> intList = Arrays.asList(10,20,30,40,50,60,70,80,90,100);
intList.stream().forEach(integer -> {
if(flushCount[0] > 5){
intList.clear();
}
flushCount[0] ++;
System.out.println(flushCount[0]);
});
}
다음 코드에서는 오류가 발생하지 않고 정상적으로 동작하게 됩니다.
일반적으로 람다식에서 변수를 참조할 때 지역변수는 읽을 수 있으나(final 포함) 쓸 수 없지만 배열은 예외다?!!!!!
그 이유가 무엇일까요?
람다 캡처링
람다식 캡처링은 람다식 외부에서 정의된 변수를 참조하는 변수를 람다식 내부에 저장하고 사용하는 동작을 의미합니다.
초기 오류에서 지역변수는 값에 변동을 주지 못했지만 배열같은 경우 값을 변경할 수 있었습니다. 그 이유는 바로 람다의 메모리 구조로 인한 현상 때문입니다.
flushCount가 지역변수일 때 스택영역에서 생겨나는데 람다는 실행될때 새로운 스택을 생성하고 기존 실행되던 스택의 변수들을 복사해서 사용하게 됩니다.
아시다시피 스레드는 스택을 공유하지 않기 때문에 람다가 별도의 스레드에서 실행된 후 flushCount변수를 할당한 스레드가 사라지면 스택 메모리에 할당된 flushCount가 해제 되었는데도 해당 변수에 참조하려고 할 것입니다. 그런 문제 때문에 변수에 접근을 허용하지 않고 복사해서 실행하게 되고 그 과정에서 기존 flushCount의 값 변동을 허용하지 않고자 하는 제약이 있습니다.
즉, flushCount가 지역변수이고 람다는 해당 값을 복사해서 사용해야 하기 때문에 복사한 지역변수의 값을 바꾸더라도 기존의 lambdaTest 함수의 헤제된 flushCount 변수에 다시 반영할 방법이 없게 되어 에러가 발생합니다.
그렇다면 배열은 왜 가능할까요?
컬렉션의 경우에는 처음부터 힙 메모리에 있기 때문에 람다 캡처링시 해당 주소값을 복사하기 때문입니다.
한번 주소를 참조하면 그 주소값을 변경하지 않고 그 주소값을 이용해서 컬랙션의 값을 변경하기 때문에 람다가 사실상 복사한 값이 변경되지 않는다고 볼 수 있습니다.
해결
자주 사용하는 람다식에서 외부의 변수를 참조해서 사용해야 하는 경우도 있기 때문에 해결 방법은
위에서 예시로 만든 컬렉션을 이용한 코드를 참조해서 힙 메모리 주소를 참조하도록 하여 값을 바꾸거나 할 수 있습니다.
오늘은 람다 캡처링에 대해 알아보았습니다.
점점 람다식을 사용해서 코딩을 해야하는 중요성을 느낌니다.
그 과정에서 발생하는 문제를 해결해 나가면서 사용해봅시다.
그럼 다음 시간에 만나요.
'Programming' 카테고리의 다른 글
[Java] 싱글톤 패턴의 활용과 한계 (0) | 2023.09.01 |
---|---|
[VUE3.JS] SFC 구조에 대해서 알아보자 (0) | 2023.09.01 |
[Magento] Magento (0) | 2023.08.29 |
[Vue] watch 속성과 옵션 (0) | 2023.08.28 |
Prometheus, Thanos란? (0) | 2023.08.23 |
댓글