포스트

[Java] 익명클래스와 람다식에서 this가 다른 이유

크루들과 모던자바 스터디를 하면서 2장을 담당하게 됐다.

메인 내용은 동작 파라미터화였고, 그걸 수행하기 위해 익명 클래스를 사용하고 Java8 이상에서는 함수형 인터페이스를 사용한 람다식으로 구현했다.

이때 책 곁가지로 나오는 내용 중, 람다식과 익명 클래스는 this 참조가 다르다고 한다. 사용 형태를 보면 익명 클래스 = 람다식인데, 왜 이런 차이가 나오는걸까? 바이트코드를 통해 자세히 알아봤다.

this?

Java에서 this는 인스턴스화된 객체의 자신의 주소를 가리킨다.

확인해보기 위해서 Apple클래스 구현 후, this를 출력하도록 구현한 printThis()를 사용해 호출해봤다.

정확하게 같은 주소를 가리킨다.

이처럼, 클래스의 this는 인스턴스화된 자신의 주소를 가리킨다는 것을 알 수 있다. 그렇다면 익명클래스와 람다식은 무엇이 다르길래 this 참조에 차이가 있는걸까?

JVM에서 익명 클래스의 구현

익명 클래스(를 포함한 모든 중첩 클래스)는 컴파일 시, JVM 내부적으로 .class 파일을 만들어 구현된다. 바이트코드를 확인해보자.

OuterClass의 중첩 멤버로 OuterClass$1 이 생긴 것을 볼 수 있고 아래서 생성자까지 불러와 초기화 하는 것을 볼 수 있다. 즉 내부적으로 OuterClass$1.class 가 생성됐을 것이다.

익명 클래스의 바이트 코드를 더 살펴보면 어떻게 내부적으로 구현되어 있는지 볼 수 있다.

익명클래스는 JVM에서 invokespecial 명령어를 통해 생성된다. invokespecial 는 생성자를 사용될때 호출되니, 내부적으로 익명 클래스를 초기화하는 행동이다. 즉, 실제 클래스를 생성하는 것과 동일하다.

JVM에서 람다식의 구현

람다식은 익명클래스처럼 실제 클래스 파일이 생기지 않는다.

간단하게 생각하면, 실제 클래스 파일이 생기지 않으니 참조할 자신의 this가 없는 것은 당연하다.

람다식은 컴파일 타임에 정적으로 인스턴스를 생성하는 것이 아닌, invokedynamic 명령어를 사용해 런타임에 람다식의 인스턴스를 생성한다.

익명 클래스처럼 외부 클래스의 중첩 멤버로 들어가지 않았고, invokespecial이 아닌, invokedynamic를 통해 생성됐음을 볼 수 있다.

그렇다면 바이트코드를 통해, 람다식이 자신의 참조를 갖지 않는다는 것을 어떻게 확인할까? 람다식에서 외부의 아무 값도 캡쳐하지 않고 바이트코드를 확인해보자.

람다식 내부에서 외부의 아무 값을 캡쳐하지 않는다면, ALOAD0이 사라진다.

만약 람다식이 자체 참조를 가지고 있었다면, 아무값을 캡쳐하지 않아도 ALOAD0 이 남아있어야 정상일 것이다.



이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.