yongsa0221의 고물상
Java 함수형 프로그래밍 - Effectively Final 본문
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
Lambda Expressions (The Java™ Tutorials > Learning the Java Language > Classes and Objects)
The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Dev.java for updated tutorials taking advantag
docs.oracle.com
Labmda식을 사용하다 보면 가끔
"Variable used in lambda expression should be final or effectively final"
라는 문구를 볼 수 있다.
람다의 스코프 외부의 변수를 변경하려면 Intellij에서 알아서 경고를 띄워주는 문구이다.
Effectively Final은 무엇이고 왜 Effectively Final이어야 할까?
Effectively final?
final 키워드를 명시적으로 붙이지 않았지만, 그 변수의 값이 초기화된 이후로 변경되지 않아서 사실상 final처럼 동작하는 경우를 effectively final이라고 한다.
람다식 내에서 변수는 effectively final 혹은 final로 선언되어 있어야 한다.
effectively final 여부는 Java 컴파일러가 판단한다. 컴파일러는 변수의 선언 이후 변경 여부를 분석하여, 값이 변경되지 않는 변수는 final 키워드가 없어도 사실상(final처럼 동작하는) 불변으로 간주한다.
interface FunctionalInterface {
int doSomething();
}
public class Main {
public static void main(String[] args) {
int local = 0;
FunctionalInterface functionalInterface = () -> {
local++; // not effectively final : compile불가!
return local;
};
}
}
위의 코드에서 local 을 람다식에서 재할당 하고 있으므로 더이상 local은 effectively final하지 않아 컴파일 오류를 마주하게 된다.
왜 lambda식 내에서는 effecctively final이어야 하는가?
이를 이해하기 위해서는 먼저 Java의 캡처(capture)에 대해 알고 넘어가야 한다.
Java의 캡처(Capture)는 람다식이나 익명 클래스가 외부의 변수를 사용할 때 이를 내부적으로 복사하여 처리하는 동작을 의미한다 이를 통해 외부 변수의 값을 "캡처"하여 람다식이나 익명 클래스 내에서 사용할 수 있다.
캡처의 핵심 개념
- Effectively Final:
- 람다식에서 사용되는 외부 변수는 effectively final이어야 한다. 즉, 변수는 초기화된 후 값이 변경되지 않아야 한다.
- 외부 변수의 상태가 변경되지 않으므로, 병렬 처리나 다중 스레드 환경에서도 안전하게 사용이 가능하다.
- 값 캡처(Value Capture):
- 람다식이 외부 변수에 접근하면, Java는 이 변수를 내부적으로 복사하여 람다식 내부에서 사용한다
- 캡처된 값은 람다식이 실행될 때 변하지 않는다.
- 참조 캡처(Reference Capture):
- 람다식이 외부 객체의 인스턴스 변수나 정적 변수에 접근할 경우, 참조를 캡처한다. 이 경우 외부 객체가 변경되면 람다식에서도 변경된 값을 참조한다.
그럼 캡처는 왜 필요한 것일까?
람다식은 힙(Heap) 영역에 생성되지만 외부 메서드의 지역 변수는 스택(Stack) 영역에 존재하기 때문에 메서드 호출이 종료되면 스택 메모리에서 사라진다. 따라서 외부 메서드의 지역 변수를 그대로 참조할 수 없게 된다.
이 문제를 해결하기 위해 Java에서는 캡처를 통해 람다식이 참조하는 외부의 지역 변수 값을 복사하여 힙에 저장하는 방식으로 외부 변수의 값이 복사하여 람다식 내부에서 고정된 값처럼 사용한다.
복사된 값은 변경되지 않는 불변의 값이어야만 참조와 값의 일관성을 유지할 수 있기 때문에 final 또는 effectively final 조건이 강제되는 것이다.
interface FunctionalInterface {
int doSomething();
}
public class Main {
public static void main(String[] args) {
int local = 0;
FunctionalInterface captureFunctionalInterface = () -> local; // capture를 통해 지역변수 local값을 획득
FunctionalInterface nonCaptureFunctionalInterface = () -> 1; // capture가 필요없는 non capture lambda
captureFunctionalInterface.doSomething();
nonCaptureFunctionalInterface.doSomething();
}
}
람다식이 힙(Heap) 영역에 생성되는 이유는?
함수형 프로그래밍 패러다임의 일부를 Java와 같은 객체지향 언어에 통합하려는 시도의 결과라고 볼 수 있다. 함수형 프로그래밍의 핵심은 함수가 일급 시민(First-Class Citizen)으로 취급되는 것이다. 즉, 함수가 데이터처럼 변수에 저장되거나, 매개변수로 전달되거나, 반환값으로 사용될 수 있어야 한다.
Java의 람다식은 함수형 프로그래밍의 이러한 개념을 어느 정도 지원한다.
- 람다식을 변수에 저장할 수 있음.
- 람다식을 매개변수로 전달하거나 반환값으로 사용할 수 있음.
java는 본질적으로 객체지향 언어이기에 모든 실행 단위는 객체로 모델링되어야 한다. 람다식 역시 이 철학에 따라 객체로 표현된다.
즉, Java의 람다식은 함수 자체를 나타내기보다는 함수형 인터페이스의 구현체 객체로 동작한다.
이는 Java가 기존 객체지향 아키텍처와의 호환성을 유지하기 위한 선택이라고 볼 수 있다.
'Java' 카테고리의 다른 글
ArrayDeque vs LinkedList (0) | 2024.10.21 |
---|