스트림을 사용하기 이전에 'List의 요소 중 가장 큰 값'을 구하기 위해 다음과 같은 코드를 구현했었습니다.
int maxValue = Integer.MIN_VALUE;
List<Integer> numbers = List.of(1,5,3,7);
for (Integer number : numbers) {
if(number > maxValue) {
maxValue = number;
}
}
System.out.println("maxValue = " + maxValue); // maxValue = 7
위 코드를 Stream으로 변경시키려 다음과 같은 코드를 짜면
int maxValue = Integer.MIN_VALUE;
List<Integer> numbers = List.of(1,5,3,7);
numbers.stream()
.forEach(number -> {if(number>maxValue){
maxValue = number;
}});
System.out.println("maxValue = " + maxValue);
maxValue에 대해 Variable used in lambda expression should be final or effectively final
라는 에러가 발생합니다.
물론 단순히 Integer형태의 stream에서 최댓값을 얻는 방법은 간단합니다.
int maxValue = Integer.MIN_VALUE;
List<Integer> numbers = List.of(1,5,3,7);
Integer result = numbers.stream()
.reduce(maxValue,Integer::max);
System.out.println("max = " + result); // max = 7
여기서는 최댓값 찾기에 집중하기보다 위에서 작성한 코드가 왜 동작하지 않는지
를 중심으로 살펴보겠습니다.
해당 코드에서 발생한 Variable used in lambda expression should be final or effectively final
에러의 의미는 maxValue라는 변수가 명시적으로 final로 선언되어 있거나
, 실질적으로 final이 선언된 변수와 같아야
한다는 의미입니다.
한번 할당된 후 값이 바뀌지 않아야 한다
는 의미입니다.에러가 발생한 이유는 1. maxValue라는 변수가 지역변수여서
, 2. maxValue라는 변수의 값을 변경하려고 해서
입니다.
maxValue가 지역변수여서 해당 에러가 발생했다는 말은, maxValue가 지역변수가 아니면 이와 같은 문제가 발생하지 않는다는 의미입니다.
public class Temp {
static int staticVariable = Integer.MIN_VALUE;
int instanceVariable = Integer.MIN_VALUE;
void func1(){
List<Integer> numbers = List.of(1,5,3,7);
numbers.stream()
.forEach(number -> {if(number>staticVariable){
staticVariable = number;
}});
}
void func2(){
List<Integer> numbers = List.of(1,5,3,7);
numbers.stream()
.forEach(number -> {if(number>instanceVariable){
instanceVariable = number;
}});
}
// 에러
void func3(){
int localVariable = Integer.MIN_VALUE;
List<Integer> numbers = List.of(1,5,3,7);
numbers.stream()
.forEach(number -> {if(number>localVariable){
localVariable = number;
}});
}
}
지역변수 maxValue의 값을 변경하려고만 하지 않으면 에러가 발생하지 않습니다.
int maxValue = Integer.MIN_VALUE;
List<Integer> numbers = List.of(1,5,3,7);
numbers.stream()
.forEach(number -> {if(number>maxValue){
System.out.println(maxValue);
}});
System.out.println("maxValue = " + maxValue);
자유 변수
를 활용할 수 있습니다. 이처럼 파라미터로 넘겨받은 데이터가 아닌 람다식 외부에서 정의된 변수를 람다식 내부에서 활용하는 걸 람다 캡처링
이라고 합니다.람다 캡처링은 왜 필요할까요? 왜 우리는 람다식 외부의 지역변수를 사용하려면 final또는 final과 같이 사용해야만 할까요?
그 이유는 지역변수가 스택
에 저장되기 때문입니다. 지역변수를 할당한 쓰레드가 사라져 변수 할당이 해제되더라도, 람다를 실행한 다른 쓰레드에서는 여전히 그 변수에 접근할 수 있어야
합니다. (이는 람다에서 쓰레드가 다른 지역 변수에 바로 접근할 수 있음을 가정합니다)
즉, 해당 변수를 사용해야 하는데 이미 값이 사라져버리는 상황을 막기 위해 자바에서는 원래 변수에 접근을 허용하지 않고 그 복사본을 전달
합니다.
복사본이기 때문에 쓰레드와 함께 지역변수의 할당이 해제되더라도 여전히 람다식을 사용할 수 있고, 복사본이기 때문에 값이 바뀌지 말아야 합니다.
그래서 지역변수를 람다식 안에서 사용하려면 final로 선언하거나, 그 값을 변경하지 않아야
하는 것이였습니다.
이러한 지역변수의 제약은 외부 변수의 값을 변환시키는 일반적인 '명령형 프로그래밍 패턴'을 제약하는 효과를 가져다주기도 합니다. 아마 저같이 코드를 짜는 사람들을 막는 효과를 가져다준 거겠죠.
람다 캡처링을 알게 되었으니 앞으로는 아래와 같은 엉뚱한 코드를 짜지 않도록 주의해야 겠습니다.
int maxValue = Integer.MIN_VALUE; // 람다식에 파라미터로 넘겨지지 않음 (자유 변수)
List<Integer> numbers = List.of(1,5,3,7);
// 람다식 안에서 자유 변수를 사용하려면 해당 변수가 final로 선언되어 있거나,
// 람다식 안에서 자유 변수의 값을 변경하지 않아야 한다!
numbers.stream()
.forEach(number -> {if(number>maxValue){
maxValue = number;
}});
System.out.println("maxValue = " + maxValue);
https://bugoverdose.github.io/development/lambda-capturing-and-free-variable/
https://thalals.tistory.com/353