#1. 개요
하나의 메서드 내부에서 사용되는 람다에서 사양하고 있는 메서드의 지역변수의 상태를 변경이 불가능하다. 왜 그럴까? 이해를 더 돕기위해 아래에 코드를 첨부한다.
'Variable 'a' is accessed from within inner class, needs to be final or effectively final' 이라는 경고문이 나오면 컴파일이 불가능하다. 위의 문장을 해석해보면 아래와 같다.
변수 'a'는 내부 클래스 내에서 액세스되며 final이거나 final 처럼 사용되어야 합니다.
간단히 말하면 해당 변수 a는 변경되면 안된다는 말이다. 왜 그럴까?
#2. 자유변수
이유는 자유변수 때문이다.
자유변수 : 익명함수에서 처럼 메서드 자신 말고 외부에서 정의된 변수
이와 같은 자유변수를 람다 내부에서 사용하는 것을 람다 켭쳐링이라고 부른다.
public void test(){
int a = 5;
Runnable r = () -> System.out.println(a);
}
// 익명함수 버전
public void test(){
int a = 5;
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(a);
}
};
}
위의 코드는 람다 캡쳐링을 하는 코드이다. 복사해서 사용하기때문에 stack영역이 다름에도 사용이 가능하다. 캡쳐링은 복사본이라고 보면된다. 하지만 이 켭쳐링(복사본)에는 제약이 있다. 인스턴스변수와 정적변수는 자유롭게 사용이 가능하다. 하지만 지역변수의 경우에는 final가 명시적으로 선언되어있거나 람다에서 사용될때 final인 것 처럼 사용되어야한다. 변경이 불가능하다는 말이다. 다시말해 람다 혹은 익명함수에서는 컴파일 시에 딱 한번만 할당 될 수 있는 지역 변수를 캡쳐해서 사용할 수 있다.
위와 같은 제약때문에 맨 위에 발생한 문제가 발생한다.
#3. 제약의 이유
간단히 말하면 메모리상 저장위치 때문이다. 인스턴스변수와 정적변수의 경우에는 모든 쓰레드가 공유가능한 heap과 method area에 저장된다. 하지만 지역변수의 경우 stack에 저장된다. stack영역은 각 쓰레드마다 생성되기 때문에 공유가 불가능하다. 만약, 람다에서 사용하는 자유 지역 변수(외부에서 정의된 변수)를 참조해서 사용할 수 있다고 가정해보자. 람다는 ThreadB에 선언되어있고 자유 지역변수는 ThreadA의 stack에 선언되어져있다. 람다에서 해당 변수를 참조해서 사용한다가 ThreadA가 모든일을 마치고 종료되었다. 그럼에도 여전히 람다는 해당 자유 지역변수를 바라보고 있고 람다가 실행될 경우 에러가 발생한다.
따라서 자바 구현에서는 원래 변수에 직접 접근을 허용하는 것이 아니라 지역 변수의 복사본을 제공한다. 복사본의 값이 바뀐다면 일관성이 유지되지 않는다. 따라서 외부에서 정의된 지역변수는 final로 선언된 함수 처럼 사용해야한다.
#4. 결론
지역변수를 사용하는 것은 상수를 사용하는 것 처럼 사용하면 된다. 하지만 람다 내부에서 자유변수(외부에서 선언한 지역변수) 값을 변경하려면 메모리상 위치를 변경해주면된다. 전역변수나 인스턴스 변수로 선언하면 변경이 가능하다.
int a = 5;
public void test(){
Runnable r = () -> a++ ;
r.run();
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(a++);
}
};
}
Reference :
- modern java in action p114
- https://github.com/woowacourse-study/2022-modern-java-in-action/issues/22
'about.Programing > Java' 카테고리의 다른 글
syncronized 키워드 (0) | 2022.07.05 |
---|