어느 때와 같이 평화롭게 개발을 하고 있던 바로 그때 ,,,
Variable used in lambda expression should be final or effectively final
라는 오류가 발생했다.
상황을 조금 더 자세하게 설명해보자고 하면, 품절된 상품은 뒤로 미루기 위해 소팅 로직을 짜고 있는 상황이였다. 품절된 상품인 경우 가장 큰 상품 아이디 + 1 + sequence 로 뒤로 정렬하면 되겠다 라는 생각을 가지고 코드를 짜던 그때 오류가 발생한 것이다.
샘플 코드
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class test {
public static void main(String[] args) {
List<Long> productList = new ArrayList<>();
int quantity = 10000;
quantity ++;
productList.stream().sorted(Comparator.comparing(b->b + quantity)).collect(Collectors.toList());
}
}
ㅠㅠ
해석해보면 람다 식에 사용되는 변수는 최종 변수이거나 사실상 최종 변수여야 합니다.
라는 의미다. 해석 내용에 걸맞게 저기서 Copy 'm' to effectively final temp variable 을 누르면 람다 안에서 사용하고자 했던 변수를 copy 한 변수를 또 만들어주고 오류가 해결 된다.
근데 왜 람다 안에서 사용되는 변수는 최종 변수(final)이거나 사실상 최종 변수(effectively final) 여야만 할까 ???????????
파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 자유 변수(Free Variable) 라고 하고, 람다 바디에서 자유 변수를 참조하는 행위를 람다 캡처링 이라고 한다.
지역 변수를 람다 캡처링할 때에 해당하는 변수가 final 혹은 effectively final 이여야 하는데 그 이유는 다음과 같다.
⚠️ 지역 변수에만 제약 조건이 적용되는 이유
지역변수는 JVM 의 스택 영역에 생성되는데 스택 영역은 쓰레드 마다 별도의 영역이 생성되기 때문에 Thread 끼리 공유가 되지 않는다.
인스턴스 변수는 JVM 의 힙 영역에 생성되는데 힙 영역은 별도의 영역에 생성되기 때문에 Thread 끼리 공유가 가능하다.
➡️ 그렇기 때문에 지역 변수인 경우에만 캡처링 해서 사용한다.
람다는 별도의 Thread 에서 실행이 가능하다.
원래 지역변수가 있던 Thread 가 사라져서 해당 지역변수가 사라졌음에도 불구하고, 람다가 실행중인 Thread 는 실행중일 수도 있다.
하지만, 람다는 변수를 자기의 Thread 로 복사해서 사용하기 때문에 오류가 발생하지 않는다.
그렇기 때문에 복사한 변수가 계속해서 변경되면 안되기 때문에 final(최종 변수) 혹은 effectively final(사실상 최종 변수)여야만 한다.
왜 inner class, Lambda는 effectively final만 접근할 수 있을까.
(Java) 람다 캡처링과 final 제약조건