공부방/JAVA

익명객체와 람다는 결국엔 같은걸까?

EVO. 2023. 12. 3. 20:37

저번 글에서 아래 두개의 코드는 같은 일을 수행한다고 글을 쓴 적이 있었다.  

 

즉, 이 둘을 보면 컴파일러는 람다식을 익명객체로 변환한다고 볼 수 있다고 생각할 수도 있다. 이 둘을 컴파일하고 javap로 디컴파일 하여 과연 바이트코드가 같은 지 확인해보겠다.

 

 

결론부터 말하면 익명객체와 람다식은 서로 다르다.

바이트코드를 얼핏 봐도 위에는 INVOKESPECIAL 이라는 JVM 명령어를 쓰고 있고, 아래는 INVOKEDYNAMIC이라는 JVM 명령어를 사용함을 볼 수 있다. 이 둘의 차이점이 뭔지 간단하게(깊지 않게) 고민해봤다.

 

익명 클래스

바이트 코드 

 

1. Ex14_0의 내부 클래스인 INNERCLASS가 만들어지고 Ex14_0$1의 클래스가 새롭게 생성되었음을 위의 바이트 코드와 다음 클래스에서 확인할 수가 있다.

 

2. INVOKESPECIAL은 생성자 초기화메서드, Super 메서드 호출을 위한 JVM 명령어 이다. 즉, 익명클래스를 초기화 했음을 알 수가 있다.

 

람다

바이트코드

 

 

위 익명 클래스와 다르게 클래스를 별도로 생성하는 코드가 없다. 단, INVOKEDYNAMIC이라는 JVM 명령어를 볼 수가 있는 데, 이는 JDK 1.7부터 새롭게 생긴 JVM 명령어 이다.

 

INVOKEDYNAMIC은 Indy 라고도 한다.

 

JDK 1.7 이전에는 JVM 명령어에는 총 4가지가 있었다. invokeinterface, invokevirtual, invokestatic, invokespecial.

 

각각의 명령어들은 메서드를 호출하기 위해 컴파일단계에서 호출하는 명령어. 런타임단계에서 호출하는 명령어들이다.

런타임 접근 방식은 invokeinterface라 볼 수가 있는데, 이 호출 방식은 리플렉션 기반이므로 결과적으로 비효율적이다. 반면 컴파일 타임 솔루션은 컴파일 타임에 코드 생성에 의존하기 때문에 처리해야 할 바이트 코드가 많아져 시작 시간이 느려질 수가 있다.

 

그래서 생긴 invokedynamic 즉, Indy를 사용하면 원하는 방식으로 메서드 invoke process를 부트스트랩 할 수 있다. 다시 말해서 JVM이 호출된 동적 연산자를 처음 발견하면 리플렉션을 사용하는 것이 아닌 부트스트랩 메서드라고 하는 특수 메서드(LamdaMetafactory)를 호출하여 invoke process를 초기화 한다. 그냥 이 명령어들을 사용하면서 성능이 향상됐다고 생각하면 될 것같다.

 

더 궁금하면 다음 사이트에 가서 공부하면 된다.

https://www.baeldung.com/java-invoke-dynamic

 

JVM 명령어들

  • invokeinterface : 인터페이스 메서드를 호출하는 데 사용되며 런타임에 이 인터페이스 메서드를 구현하는 객체를 검색하고 호출할 적절한 메서드를 찾는다.
  • invokevirtual : 이 명령어는 객체의 인스턴스 메서드를 호출하고 객체의 실제 유형에 따라 디스패치하는 데 사용
  • invokestatic : 클래스 메서드를 호출하는 데 사용(클래스(정적) 메서드 호출)
  • invokespecial : 인스턴스 초기화 메서드, 부모 클래스 메서드 등 특별한 처리가 필요한 일부 인스턴스 메서드를 호출하는 데 사용(참고로  private메서드는 이제 invokevirtual 명령어로 호출된다)
  • invokedynamic : JDK 1.7에서 새로운 가상머신 명령어가 추가됐다. 동적 메서드를 호출할 때 사용. 람다식도 이에 포함된다.

예시)

 

레퍼런스