Java의 MethodHandle 심층 분석
MethodHandle은 자바 7에서 도입된 핵심 API(java.lang.invoke 패키지 위치)로, 동적 메서드 호출 메커니즘을 제공합니다. 반사(reflection)와 유사하지만 더 효율적이고 유연한 특징을 가집니다.
기본 개념
MethodHandle(메서드 핸들)은 기본 메서드, 생성자, 필드 등에 대한 추상 참조로, 컴파일 시점에 구체적인 메서드 시그니처를 알 필요 없이 대상 메서드를 호출할 수 있게 해줍니다.
주요 특징
- 타입 안전성: 반사와 달리 MethodHandle은 컴파일 시점에서 타입 검사를 수행합니다
- 고성능: JVM이 MethodHandle 호출에 특별한 최적화를 적용하여 반사보다 일반적으로 더 빠릅니다
- 유연한 호출: 다양한 호출 방식(invokeExact, invoke 등)을 지원합니다
- 메서드 조합: 여러 메서드 핸들을 조합하여 호출 체인을 형성할 수 있습니다
핵심 클래스와 메서드
MethodHandles.Lookup
메서드 핸들을 찾고 생성하는 주요 진입점입니다:
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle 생성 방법
- 인스턴스 메서드 찾기:
MethodHandle mh = lookup.findVirtual(String.class, "substring",
MethodType.methodType(String.class, int.class, int.class));
- 정적 메서드 찾기:
MethodHandle mh = lookup.findStatic(Math.class, "sqrt",
MethodType.methodType(double.class, double.class));
- 생성자 찾기:
MethodHandle mh = lookup.findConstructor(String.class,
MethodType.methodType(void.class, byte[].class));
- 필드 접근자 찾기:
// 필드 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 매개변수를 받는 메서드를 의미
메서드 호출
- 정확한 호출 (invokeExact):
String result = (String) mh.invokeExact("hello", 1, 3);
- 메서드 시그니처와 완벽히 일치해야 합니다
- 더 높은 성능을 제공합니다
- 일반 호출 (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(); // 수신자 매개변수 전달 불필요
주의사항
- MethodHandle 호출은 직접 메서드 호출보다 약간 느리지만, 반사보다는 빠릅니다
invokeExact사용 시 메서드 시그니처와 완벽히 일치해야 합니다- Java 8 이상 버전에서 람다 표현식과 메서드 참조는 내부적으로 MethodHandle로 구현됩니다
- 간단한 사용 사례의 경우, 직접 메서드 호출이나 람다가 더 나은 선택입니다
MethodHandle은 강력한 동적 호출 기능을 제공하며, 특히 높은 유연성이 필요한 프레임워크와 라이브러리 개발에 적합합니다.