자바에선 안 되는데 코틀린의 람다에서 final이 아닌 외부 변수를 변경할 수 있는 이유는?

Jaychy·2021년 6월 22일
0

알아가는 것

목록 보기
5/11
post-thumbnail

Github 코틀린으로 개발하면서 궁금했던 점에 대해 고찰하는 곳.

개요

자바에서의 람다식이나 익명 함수에서는
final이거나 effectively final인 외부 지역 변수만 접근할 수 있습니다.
하지만 코틀린의 람다식이나 익명 함수에서는 굳이 final이 아니더라도 (val이 아니더라도)
var로 선언된 지역 변수에 접근할 수 있는 것을 볼 수 있습니다.
자바에서는 안 되는데 어떻게 코틀린에서 할 수 있는 것일까요?

문제의 상황 알아보기

fun interface TestInterface {
    fun test()
}

위와 같은 `SAM Interface가 존재할 때

fun main() {
    var count = 0

    val test = TestInterface {
        count++
    }
    test.test()
    test.test()

    println(count)
}

위와 같은 코드가 실행이 됩니다.
TestInterface를 람다식으로 구현할 때
람다식 내부에서 지역 변수인 count를 수정하는 것이 가능합니다.
자바에서는 실패하지만 코틀린에서는 성공합니다.

자바 코드로 디컴파일 해보기

main 함수를 자바로 디컴파일 해보면 대충 다음과 같이 변경됩니다.

public final class MainKt {
    public static final void main() {
        IntRef intRef = new IntRef(0);
        
        TestInterface test = () -> intRef.element++;
        test.test();
        test.test();

        System.out.println(intRef.element);
    }
}

이렇게 자바로 변경할 경우 지역 변수를 IntRef라는 객체로 감싸고
객체의 내부를 변경하는 식으로 우회하는 것을 알 수 있습니다.
자바에서 이렇게 코드를 작성하게 되면 지역 변수가 Thread-safe하지 못하기 때문에
이렇게 작성하는 것은 위험합니다.

만약 멀티 스레드 환경에서 람다식을 사용할 때에는 외부 지역 변수를 참조할 때
반드시 val로 선언된 변수만 참조하는 것을 권장합니다.

결론

코틀린의 람다식에서 외부 지역 변수를 변경할 수 있는 이유는
외부 지역 변수를 객체로 감싸서 우회했기 때문입니다.
단, 외부 지역 변수가 Thread-safe하지 않은 상태에 놓이기 때문에
멀티 스레드 환경에서 조심해야 합니다.

profile
아름다운 코드를 꿈꾸는 백엔드 주니어 개발자입니다.

0개의 댓글