MethodHandle
是 Java 7 引入的一项重要特性,它是一种用于在运行时执行方法调用的强大而灵活的机制。MethodHandle
的设计目标是提供比反射更高效的方法调用,并且在某些情况下,它比传统的 Java 方法调用更灵活。
使用示例
java
public class MethodHandleExample {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 定义方法类型
MethodType methodType = MethodType.methodType(void.class, String.class);
// 获取 MethodHandle,指定方法名、方法类型
MethodHandle methodHandle = lookup.findVirtual(MethodHandleExample.class, "printMessage", methodType);
// 创建实例
MethodHandleExample example = new MethodHandleExample();
// MethodHandle调用 printMessage 方法
methodHandle.invokeExact(example, "Hello, MethodHandle!");
}
// 示例方法
public void printMessage(String message) {
System.out.println(message);
}
}
输出
Hello, MethodHandle!
MethodHandles.Lookup
MethodHandles.Lookup 构造函数不是公有的,所以只能用静态工厂方法获取
java
private Lookup(Class<?> lookupClass, int allowedModes) {
this.lookupClass = lookupClass;
this.allowedModes = allowedModes;
}
MethodHandles 提供了三种获取 MethodHandles.Lookup 对象的方法这三种主要区别在于权限检查
- 权限检查除了 allowedModes
- 还需要检查 lookupClass : 检查lookupClass 这个类是否能够访问目标方法
两种都能够检测通过方法才能调用成功
java
// 允许调用所有方法
MethodHandles.Lookup lookup = MethodHandles.lookup();
//只能调用 public 方法
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
// 通过 privateLookupIn 方法可以重新构建一个 MethodHandles.Lookup 对象
MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(clz, lookup);
MethodType
MethodType
是 Java 中用于描述方法类型的类。它包含了方法的返回类型、参数类型等信息,提供了一种表示方法签名的方式。MethodType
主要用于创建 MethodHandle
,以确定方法句柄的类型
MethodType 构造函数是私有,所以只能用静态工厂方法获取
java
// 返回类型,参数类型
private MethodType(Class<?> rtype, Class<?>[] ptypes) {
this.rtype = rtype;
this.ptypes = ptypes;
}
java
// 返回类型
public static MethodType methodType(Class<?> rtype) {
return makeImpl(rtype, NO_PTYPES, true);
}
// 返回类型,一个参数
public static MethodType methodType(Class<?> rtype, Class<?> ptype0) {
return makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
}
// 返回类型,多个参数
public static MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) {
Class<?>[] ptypes1 = new Class<?>[1+ptypes.length];
ptypes1[0] = ptype0;
System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length);
return makeImpl(rtype, ptypes1, true);
}
// 返回类型,多个参数
public static MethodType methodType(Class<?> rtype, Class<?>[] ptypes) {
return makeImpl(rtype, ptypes, false);
}
// 返回类型,多个参数
public static MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) {
boolean notrust = false; // random List impl. could return evil ptypes array
return makeImpl(rtype, listToArray(ptypes), notrust);
}
// 返回类型,通过 MethodType 获取参数类型
public static MethodType methodType(Class<?> rtype, MethodType ptypes) {
return makeImpl(rtype, ptypes.ptypes, true);
}
// 返回类型是Object ,参数类型都是 Object,参数个数
public static MethodType genericMethodType(int objectArgCount) {
return genericMethodType(objectArgCount, false);
}
// 返回类型是Object ,参数类型都是 Object,或者 Object[]
// 参数个数 objectArgCount ,finalArray 决定最后一个参数是否 Object[]
public static MethodType genericMethodType(int objectArgCount, boolean finalArray) {
MethodType mt;
checkSlotCount(objectArgCount);
int ivarargs = (!finalArray ? 0 : 1);
int ootIndex = objectArgCount*2 + ivarargs;
if (ootIndex < objectOnlyTypes.length) {
mt = objectOnlyTypes[ootIndex];
if (mt != null) return mt;
}
Class<?>[] ptypes = new Class<?>[objectArgCount + ivarargs];
Arrays.fill(ptypes, Object.class);
if (ivarargs != 0) ptypes[objectArgCount] = Object[].class;
mt = makeImpl(Object.class, ptypes, true);
if (ootIndex < objectOnlyTypes.length) {
objectOnlyTypes[ootIndex] = mt; // cache it here also!
}
return mt;
}
// 通过字符串描述方法签名来获取
public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
throws IllegalArgumentException, TypeNotPresentException
{
return fromDescriptor(descriptor,
(loader == null) ? ClassLoader.getSystemClassLoader() : loader);
}
可使用 javap 命令获取签名
以下是通过方法签名获取 MethodType 示例
java
public void generateMethodTypesFromDescriptor() {
ClassLoader cl = this.getClass().getClassLoader();
String descriptor = "(Ljava/lang/String;)Ljava/lang/String;";
MethodType mt = MethodType.fromMethodDescriptorString(descriptor, cl);
System.out.println(mt);
}
MethodHandle
MethodHandle 属于抽象类,可通过 MethodHandles.Lookup 对象方法获取
- 查找静态方法句柄
获取静态方法的方法句柄。(由于静态方法不接收接收者,因此没有像findVirtual或findSpecial那样将其他接收者参数插入方法句柄类型。)该方法及其所有参数类型必须对查找对象是可访问的。
java
/*
参数:
refc-从中访问方法的类
name-方法名称
type-方法的类型
*/
public MethodHandle findStatic(Class<?> refc, String name, MethodType type)
- 查找虚方法句柄
获取虚拟方法的方法句柄。 方法句柄的类型将是方法的类型,并带有接收者类型(通常是refc)。 该方法及其所有参数类型必须对查找对象是可访问的。
java
/*
refc-从中访问方法的类或接口
name-方法名称
type-方法的类型,省略接收方参数
*/
public MethodHandle findVirtual(Class<?> refc, String name,MethodType type)
- 查找构造方法
java
/*
参数:
refc-从中访问方法的类或接口
type-方法的类型,省略了接收器参数,并返回 void
*/
public MethodHandle findConstructor(Class<?> refc, MethodType type)
- 查找构造方法、私有方法和父类中的方法句柄
注意:即使在特殊情况下,invokespecial指令可以引用名为
<init>
的JVM内部方法,该API也不可见。使用findConstructor以安全的方式访问实例初始化方法。)
java
/*
参数:
refc-从中访问方法的类或接口 (定义的方法的类,父类)
name-方法的名称(不能为" <init>")
type-方法的类型,省略接收方参数
specialCaller-建议的调用类以执行invokespecial (实际调用的方法的类)
*/
public MethodHandle findSpecial(Class<?> refc,String name,MethodType type,Class<?> specialCaller)
- 获取对象字段值
java
/*
参数:
refc-从中访问方法的类或接口
name-字段名称
type-字段的类型
*/
public MethodHandle findGetter(Class<?> refc,String name,Class<?> type)
- 设置对象字段值
java
/*
参数:
refc - 从中访问方法的类或接口
name - 字段名称
type - 字段的类型
*/
public MethodHandle findSetter(Class<?> refc, String name,Class<?> type)
- 获取静态字段值
java
/*
参数:
refc - 从中访问方法的类或接口
name - 字段名称
type - 字段的类型
*/
public MethodHandle findStaticGetter(Class<?> refc, String name,Class<?> type)
- 设置静态字段值
java
/*
参数:
refc-从中访问方法的类或接口
name-字段名称
type-字段的类型
*/
public MethodHandle findStaticSetter(Class<?> refc,String name, Class<?> type)
- 绑定
java
/*
参数:
receiver-从中访问方法的对象
name-方法名称
type-方法的类型,省略接收方参数
*/
public MethodHandle bind(Object receiver, String name,MethodType type)
- 普通反射方法转换为方法句柄
java
/*
参数:
m - 反射方法
*/
public MethodHandle unreflect(Method m)
- Special转化为方法句柄
java
/*
参数:
m-反射方法
specialCaller-名义上调用该方法的类
*/
public MethodHandle unreflectSpecial(Method m,
Class<?> specialCaller)
- 构造函数转换为方法句柄
java
/*
参数:
c-反射构造函数
*/
public MethodHandle unreflectConstructor(Constructor<?> c)
- 获取反射字段转化为方法句柄
java
/*
参数:
f-反射字段
*/
public MethodHandle unreflectGetter(Field f)
- 设置反射字段转化为方法句柄
java
/*
参数:
f-反射字段
*/
public MethodHandle unreflectSetter(Field f)
- 调用方法
java
// 在使用 `invoke` 时,参数的数量和类型可以与方法句柄的类型不完全匹配,
// 因为它会尝试在运行时适应目标方法的类型。
/*
参数:
args-签名多态参数列表,使用varargs静态表示
*/
public final Object invoke(Object... args);
// 方法要求参数的数量和类型与方法句柄的类型精确匹配,包括返回类型
// 如果参数类型和数量不精确匹配,将抛出 `WrongMethodTypeException` 异常。
/*
参数:
args-签名多态参数列表,使用varargs静态表示
*/
public final Object invokeExact(Object... args);
// invokeWithArguments 会在运行时检查参数的数量和类型,并进行必要的类型转换,
// 但要求参数列表的总数必须与方法句柄类型的参数数量匹配。
/*
参数:
args-签名多态参数列表,使用varargs静态表示
*/
public Object invokeWithArguments(Object... args);
/*
参数:
args-参数可以用 List 传递参数
*/
public Object invokeWithArguments(List<?> args);
VarHandle
java.lang.invoke.VarHandle
是 Java 9 引入的一个新的 API,用于提供对数组和字段的原子操作、比较和交换等操作。它提供了对内存中的数据进行低级别操作的能力,以替代传统的 java.util.concurrent.atomic
和 sun.misc.Unsafe
类。
a. 针对数组元素的 VarHandle:
java
public class VarHandleArrayExample {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
int[] array = new int[10];
// 获取数组元素的 VarHandle
VarHandle arrayElementVarHandle = MethodHandles.arrayElementVarHandle(int[].class);
// 使用 VarHandle 读写数组元素
arrayElementVarHandle.set(array, 1, 42);
int value = (int) arrayElementVarHandle.get(array, 1);
System.out.println("Array element at index 1: " + value);
}
}
javascript
Array element at index 1: 42
b. 针对字段的 VarHandle:
java
public class VarHandleFieldExample {
private int fieldValue;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
VarHandleFieldExample example = new VarHandleFieldExample();
// 获取字段的 VarHandle
VarHandle fieldVarHandle = MethodHandles.lookup().findVarHandle(VarHandleFieldExample.class, "fieldValue", int.class);
// 使用 VarHandle 读写字段
fieldVarHandle.set(example, 42);
int value = (int) fieldVarHandle.get(example);
System.out.println("Field value: " + value);
}
}
yaml
Field value: 42
VarHandle 的操作
VarHandle
提供了一系列的原子操作,包括读取、写入、比较并交换等。以下是一些示例:
java
public class VarHandleOperationsExample {
private int value;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
VarHandleOperationsExample example = new VarHandleOperationsExample();
VarHandle varHandle = MethodHandles.lookup().findVarHandle(VarHandleOperationsExample.class, "value", int.class);
// 读取值
int currentValue = (int) varHandle.get(example);
System.out.println("Current value: " + currentValue);
// 原子性地增加值
int newValue = (int) varHandle.getAndAdd(example, 5);
System.out.println("New value after getAndAdd: " + newValue);
// 原子性地比较并交换值
boolean success = varHandle.compareAndSet(example, currentValue, 20);
System.out.println("Compare and set success: " + success);
System.out.println("Current value after compareAndSet: " + varHandle.get(example));
}
}
sql
Current value: 0
New value after getAndAdd: 0
Compare and set success: false
Current value after compareAndSet: 5
MethodHandle 与 Reflection (反射) 的区别:
-
Reflection 和 MethodHandle 机制本质上都是在模拟方法调用,但是 Reflection 是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用 因此通常比反射更快。
-
Reflection 中的
java.lang.reflect.Method
对象远比 MethodHandle 机制中的java.lang.invoke.MethodHandle
对象所包含的信息来得多。前者是方法在Java端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的 Java 端表示方式,还包含执行权限等的运行期信息。而后者仅包含执行该方法的相关信息。用开发人员通俗的话来讲,Reflection 是重量级,而 MethodHandle 是轻量级。 -
Reflection API 的设计目标是只为Java语言服务的,而 MethodHandle 则设计为可服务于所有Java虚拟机之上的语言,其中也包括了Java语言而已,而且Java在这里并不是主角。