코딩테스트 문제를 풀어보면서 스트림을 이용하는 연습을 해보려고 했는데, 스트림 연산 안에 활용하는 람다 식에 Variable used in lambda expression should be final or effectively final 경고가 나타났다.
람다는 나중에 다루려고 했는데, 이 경고는 앞으로 스트림을 이용하는 데에서도 자주 나타날 것 같으니 미리 좀 알아둬야겠어서 글로 정리해본다.
람다 표현식을 사용할 때, 람다 표현식 내부에서 참조하는 외부 변수는 final이거나 effectively final이어야 한다. 이는 Java에서 람다 표현식의 스코프(scope)와 동작 방식에 기인한 제한이라고 한다. 이 제한이 생기는 이유와 해결 방법을 살펴보겠습니다.
final 변수: final 키워드가 붙은 변수는 초기화 후 값을 변경할 수 없다.
final int number = 5;
number = 10; // 오류 발생: final 변수는 변경 불가
effectively final 변수: final 키워드가 없더라도, 초기화 후 값이 변경되지 않은 변수, 마치 final 키워드가 붙은 변수인 듯한 해당 변수를 effectively final로 생각하면 된다.
int x = 10; // x 값은 이후로 변경되지 않음
람다 표현식에서는 이 final, effectively final 변수로만 접근할 수 있습니다.
Java의 람다 표현식은 내부적으로 익명 클래스와 유사하게 동작한다. 익명 클래스 내부에서는 상위 스코프의 변수를 직접 접근하고 수정할 수 없으며, 해당 변수가 상위 스코프에서 수정되지 않는 상수인 것처럼 취급한다. Java는 이와 같은 제한을 통해 동기화(synchronization)나 예측하지 못한 동작을 방지한다고 한다.
람다 표현식이 실행되는 동안 외부의 변수가 변경될 경우, 코드가 실행될 때마다 변수가 다르게 작용하여 예측하지 못한 버그가 발생할 수 있고, 이를 방지하려고 람다 표현식 내에 외부 변수의 참조가 안전하도록 final을 강요하는 것이다!
아래 코드는 연습하면서 경고를 발생시켰던 내 코드이다.
public int[] solution(int[] answers) {
int[][] patterns = {
{1, 2, 3, 4, 5},
{2, 1, 2, 3, 2, 4, 2, 5},
{3, 3, 1, 1, 2, 2, 4, 4, 5, 5}
};
int maxScore = Integer.MIN_VALUE;
List<Integer> scores = new ArrayList<>();
for (int i = 0; i < patterns.length; i++) {
int scored = 0;
for (int j = 0; j < answers.length; j++) {
int[] currentPattern = patterns[i];
if (currentPattern[j % (currentPattern.length - 1)] == answers[j]) {
scored++;
}
}
scores.add(scored);
if (scored > maxScore) maxScore = scored;
}
return IntStream.range(0, scores.size())
.filter(i -> scores.get(i) == maxScore) // 이 블로그 글의 시작, 경고 원인
.map(i -> i + 1)
.toArray();
}
여기서 filter(i -> scores.get(i) == maxScore) 이 표현식이 문제였던 것인데, 바로 maxScore는 상위 스코프에서 마음껏 수정해왔고, 람다 식에서 또 쓰려고 하니 불편했던 것. 이를 해결하기 위해서 maxScore를 final 혹은 effectively final 변수에 한 번 저장해주고 람다에서 활용하면 된다.
int finalMaxScore = maxScore; // lambda에서 사용하기 위해 final로 선언
return IntStream.range(0, scores.size())
.filter(i -> scores.get(i) == finalMaxScore)
.map(i -> i + 1)
.toArray();
final 또는 effectively final로 제한한다.