Java의 MethodHandle 심층 분석

Java의 MethodHandle 심층 분석

MethodHandle은 자바 7에서 도입된 핵심 API(java.lang.invoke 패키지 위치)로, 동적 메서드 호출 메커니즘을 제공합니다. 반사(reflection)와 유사하지만 더 효율적이고 유연한 특징을 가집니다.

기본 개념

MethodHandle(메서드 핸들)은 기본 메서드, 생성자, 필드 등에 대한 추상 참조로, 컴파일 시점에 구체적인 메서드 시그니처를 알 필요 없이 대상 메서드를 호출할 수 있게 해줍니다.

주요 특징

  1. 타입 안전성: 반사와 달리 MethodHandle은 컴파일 시점에서 타입 검사를 수행합니다
  2. 고성능: JVM이 MethodHandle 호출에 특별한 최적화를 적용하여 반사보다 일반적으로 더 빠릅니다
  3. 유연한 호출: 다양한 호출 방식(invokeExact, invoke 등)을 지원합니다
  4. 메서드 조합: 여러 메서드 핸들을 조합하여 호출 체인을 형성할 수 있습니다

핵심 클래스와 메서드

MethodHandles.Lookup

메서드 핸들을 찾고 생성하는 주요 진입점입니다:

MethodHandles.Lookup lookup = MethodHandles.lookup();

MethodHandle 생성 방법

  1. 인스턴스 메서드 찾기:
MethodHandle mh = lookup.findVirtual(String.class, "substring", 
    MethodType.methodType(String.class, int.class, int.class));

  1. 정적 메서드 찾기:
MethodHandle mh = lookup.findStatic(Math.class, "sqrt", 
    MethodType.methodType(double.class, double.class));

  1. 생성자 찾기:
MethodHandle mh = lookup.findConstructor(String.class, 
    MethodType.methodType(void.class, byte[].class));

  1. 필드 접근자 찾기:
// 필드 getter 가져오기
MethodHandle getter = lookup.findGetter(MyClass.class, "fieldName", String.class);
// 필드 setter 가져오기
MethodHandle setter = lookup.findSetter(MyClass.class, "fieldName", String.class);

MethodType

메서드 시그니처를 나타냅니다:

MethodType type = MethodType.methodType(String.class, int.class, int.class);
// String을 반환하고 두 개의 int 매개변수를 받는 메서드를 의미

메서드 호출

  1. 정확한 호출 (invokeExact):
String result = (String) mh.invokeExact("hello", 1, 3);

  • 메서드 시그니처와 완벽히 일치해야 합니다
  • 더 높은 성능을 제공합니다
  1. 일반 호출 (invoke):
String result = (String) mh.invoke("hello", 1, 3);

  • JVM이 자동으로 매개변수 타입을 변환하려고 시도합니다
  • 더 유연하지만 약간 느립니다

메서드 핸들 조합

MethodHandle은 조작 작업을 지원합니다:

MethodHandle toString = lookup.findVirtual(Object.class, "toString", 
    MethodType.methodType(String.class));
MethodHandle stringLength = lookup.findVirtual(String.class, "length", 
    MethodType.methodType(int.class));
    
// toString 호출 후 length 호출하는 조합 체인 생성
MethodHandle toStringThenLength = MethodHandles.filterReturnValue(toString, stringLength);
int length = (int) toStringThenLength.invokeExact(new Object());

반사와의 비교

특성 MethodHandle 반사 (Reflection)
성능 더 높음 (JVM 최적화) 상대적으로 낮음
타입 안전성 컴파일 시점 검사 실행 시점 검사
유연성 높음 (조합 등 지원) 중간 수준
비공개 멤버 접근 접근 가능 설정 필요 setAccessible 필요
API 복잡도 중간 수준 단순함

실제 적용 예제

import java.lang.invoke.*;

public class MethodHandleDemo {
    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        
        // String.substring 메서드 핸들 가져오기
        MethodHandle substring = lookup.findVirtual(String.class, "substring", 
            MethodType.methodType(String.class, int.class, int.class));
        
        // 메서드 핸들 호출
        String result = (String) substring.invokeExact("Hello World", 6, 11);
        System.out.println(result); // "World" 출력
        
        // 정적 메서드 예제
        MethodHandle sqrt = lookup.findStatic(Math.class, "sqrt", 
            MethodType.methodType(double.class, double.class));
        double root = (double) sqrt.invokeExact(16.0);
        System.out.println(root); // 4.0 출력
    }
}

고급 사용법

asType을 사용한 매개변수 변환

MethodHandle intToString = lookup.findVirtual(Integer.class, "toString", 
    MethodType.methodType(String.class));
    
// int를 자동으로 Integer로 박싱
MethodHandle adapted = intToString.asType(
    MethodType.methodType(String.class, int.class));
    
String result = (String) adapted.invokeExact(123); // 자동 박싱

bindTo를 사용한 수신자 바인딩

MethodHandle toString = lookup.findVirtual(String.class, "toString", 
    MethodType.methodType(String.class));
    
// 수신자 객체 바인딩
MethodHandle bound = toString.bindTo("바인딩된 문자열");
String result = (String) bound.invokeExact(); // 수신자 매개변수 전달 불필요

주의사항

  1. MethodHandle 호출은 직접 메서드 호출보다 약간 느리지만, 반사보다는 빠릅니다
  2. invokeExact 사용 시 메서드 시그니처와 완벽히 일치해야 합니다
  3. Java 8 이상 버전에서 람다 표현식과 메서드 참조는 내부적으로 MethodHandle로 구현됩니다
  4. 간단한 사용 사례의 경우, 직접 메서드 호출이나 람다가 더 나은 선택입니다

MethodHandle은 강력한 동적 호출 기능을 제공하며, 특히 높은 유연성이 필요한 프레임워크와 라이브러리 개발에 적합합니다.

태그: java-methodhandle java-lang-invoke java-dynamic-invocation java-reflection

7월 17일 06:15에 게시됨