지난번에 람다에 대하여 정리하는 글을 올렸습니다.
남다른 람다 정리 보기!!
뭐 이런 글을 하나 올렸는데 하나의 댓글이 달렸죠.
좋은 피드백 정말 감사합니다!!
피드백에 이런 생각이 들었습니다.
'아 다음에 저 내용에 대하여 정리해볼까??'
수행평가가 많다는 핑계로 지금까지 미루다가
이제 Effectively Final
에 대하여 정리해보도록 하겠습니다.
혹시 자바에 Final
에 대해서 아시나요??
final field
파이널 필드란
만약에 필드에 final
을 붙인다면 그 필드는 더이상 수정할 수 없습니다.
public Test {
final String name = "snack655";
public changeName(String name) {
this.name = name; // 오류 발생!!
}
}
위의 코드는 요류가 나는 코드입니다.
그 이유는 당연히 final
이 붙은 필드를 변경하려고 했기 때문입니다.
그렇다면 Effectively Final
의 이름을 보시면 당연히 final
과 관련이 있겠쥬??
자바8에서 도입된 Effectively Final
이란??
변수에
final
을 붙이지 않았지만,
값이 변경되지 않아final
과 유사하게 동작하는 것
이런 Effectively Final
은 흔히 람다식 혹은 익명 클래스에서 자주 접할 수 있습니다.
그 이유는 익명 클래스 혹은 람다식에서는 참조하는 외부 지역 변수는
final
로 선언되었거나
Effectively Final
이어야 하기 때문입니다.
여기서 람다식이 무엇인지 잘 모르시겠다면
남다른 람다 정리 보기!! ㅎㅎ..
위의 말을 코드로 보신다면 더욱 이해가 잘 되실거에유!!
public void method() {
int value = 10;
Calc calc = new Calc() {
@Override
public void minus() {
value--; // 오류 발생!!
}
};
}
@FunctionalInterface
interface Calc {
void minus();
}
위와 같은 익명 클래스를 사용한 코드가 있습니다.
만약 IntelliJ를 사용하신다면 아래와 같은 오류가 발생합니다.
final
혹은 effectively final
이 필요하다고 합니다!
위의 코드를 람다식을 바꾸어 보겠습니다.
public void method() {
int value = 10;
Calc calc = () -> value--; // 오류 발생!!
}
마찬가지로 똑같은 오류가 발생합니다!
간단하게 한가지 더 보자면
int value = 10;
Calc calc = () -> System.out.println(value);
value++;
이렇게 밖에서 변경하려고 해도 마찬가지로 오류가 발생합니다.
그런데 한가지 의문이 듭니다..?
public void method() {
final int[] value = {10};
Calc calc = () -> value[0]--;
}
위의 코드에서는 오류가 발생하지 않습니다.
"아니.. 도대체
effectively final
의 조건이 정확히 뭐에유??"
그래서 조건을 한번 살펴보자면
따라서 위와 같이
배열 객체를 참조하고 있는
final int[] value = {10};
위의 코드는 안의 값을 변경해도 상관이 없는 것입니다!
람다식 내부에서 외부 지역 변수를 참조할 때 final
혹은 effectively final
을 사용해야하는 이유가 무엇일까유??
일단 아래와 같은 코드가 있습니다.
public void method() {
int value = 0;
Calc calc = () -> System.out.println(value);
}
위의 코드를 보면 람다식은 외부 지역 변수인 value
를 참조하고 있습니다.
그러면 람다 내부에서 사용할 수 있도록 똑같은 복사본을 생성합니다.
이렇게 외부의 변수를 사용할 때 람다식 내부에서 사용할 수 있도록 복사복을 생성하는 것을 람다 캡쳐링(Lambda Capturing)이라고 합니다.
이렇게 복사를 하지 않고 그냥 외부의 값을 그대로 쓴다고 가정해봅시다.
public void threadTest() {
int value = 0;
new Thread(() -> {
try {
Thread.sleep(100000);
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
threadTest()
를 돌리는 스레드가 있고
value
를 출력하는 스레드가 있습니다.
그러면
value
를 출력하는 스레드가 출력을 하기도 전에
threadTest()
를 돌리는 스레드가 끝나고 말죠.
스택 영역에 할당된 지역 변수value
는 스레드가 끝나며 함께 사라집니다.
그러면value
를 참조할 수가 없는 상황이 벌어지고 맙니다.
복사한다면 이런 문제가 발생하지 않죠!!
그런데 만약에 복사된 값이 바뀐다면 복사된 값이 최신 값임을 보장할 수 있을까요?
보장할 수 없고 이것이 바로 값이 바뀌면 안되는 이유입니다!
자바에는 클래스 변수도 있고 인스턴스 변수도 있습니다.
마찬가지 일까요??
먼저 클래스 변수와 인스턴스 변수에 대하여 간단하게 보도록 하죠.
public class EffectivelyFinal {
int a = 0; // 인스턴스 변수
static int b = 0; // 클래스 변수
public void method() {
int value = 0; // 지역 변수
}
}
간단하게 이것입니다.
여기서 인스턴스 변수는 힙 영역,
클래스 변수는 메서드 영역에 할당되죠!
따라서 람다식에서는 이 두 변수를 복사할 필요도 없는 것이죠!
따라서 변경되어도 상관없습니다!
그럼 코드로 살펴보도록 하죠!
public class EffectivelyFinal {
int a = 0; // 인스턴스 변수
static int b = 0; // 클래스 변수
public void method() {
int value = 0; // 지역 변수
Calc calc = () -> System.out.println(a--);
Calc calc2 = () -> System.out.println(b--);
Calc calc2 = () -> System.out.println(value--); // 오류 발생!!
}
}
이렇게 인스턴스 변수와 클래스 변수는 변경이 가능합니다!
짧게 Effectively final
에 대하여 정리해 보았습니다.
지식이 부족하여 부족한 부분도 있을거 같아 꽤 아쉽기도 합니다.
그래도 최대한 좋게 적고자 했습니다!!
혹시 수정이 필요할 거 같은 부분이 있다면 댓글로 알려주세요!