1.Field方式
package com.qihongze.dw.flow.joinDim;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class MyClass {
private String name;
private int age;
private double height;
public void myMethod(String message) {
System.out.println(message);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
public class test {
public static void main(String[] args) throws Exception {
// 1. 创建 MyClass 的实例
MyClass obj = new MyClass();
// 2. 调用公共方法 myMethod
Method myMethod = MyClass.class.getMethod("myMethod", String.class);
myMethod.invoke(obj, "Hello, Reflection!");
// 3. 获取并调用私有字段 name
Field nameField = MyClass.class.getDeclaredField("name");
// 也可以用下面的代码,获取obj对象的name属性Field
Field nameField = obj.getClass().getDeclaredField("name");
// 设置字段可访问
nameField.setAccessible(true);
// 使用 setter 方法设置字段值
nameField.set(obj, "John");
// 使用 getter 方法获取字段值
String name = (String) nameField.get(obj);
System.out.println("Name: " + name); // 输出: Name: John
}
}
2.MethodHandle方式
MethodHandle是 Java 7 引入的一个强大的反射机制,它提供了一种比传统的Method对象更灵活和强大的方法调用方式。与Method对象不同,MethodHandle可以像函数指针一样直接调用方法,并且支持对方法的参数和返回值进行更灵活的操作,包括类型转换、参数重排序等。
(1) 创建 MethodHandle
- 使用
MethodHandles.lookup(): - 首先,你需要使用
MethodHandles.lookup()方法来创建一个MethodHandles.Lookup对象,它是创建MethodHandle的起点。
MethodHandles.Lookup lookup = MethodHandles.lookup();
-
lookup对象提供了一系列方法来创建不同类型的MethodHandle,例如unreflect方法可以将已有的Method或Constructor转换为MethodHandle。 -
通过
unreflect系列方法创建 MethodHandle: -
从方法创建 MethodHandle:
Method method = MyClass.class.getMethod("myMethod", String.class);// 获取MyClass类下面参数类型是String的myMethod方法
MethodHandle methodHandle = lookup.unreflect(method);
- 从构造函数创建 MethodHandle:
Constructor<MyClass> constructor = MyClass.class.getConstructor(String.class);
MethodHandle constructorHandle = lookup.unreflectConstructor(constructor);
(2) 调用 MethodHandle
- 使用
invoke或invokeExact方法: invoke方法会自动进行类型转换,而invokeExact要求严格的类型匹配。
MyClass obj = new MyClass();
Method addMethod = MyClass.class.getMethod("add", int.class, int.class);
// 使用 invoke 方法,会进行类型转换,obj参数表示要调用方法的对象实例,如果是静态方法可以为null;第二个及以后参数是要调用的方法传入的参数
addMethod.invoke(obj, 3, 4);
// 使用 invokeExact 方法,要求严格的类型匹配
addMethod.invokeExact((Object) obj, (Object) "argument");
- 使用
invokeWithArguments方法,该方法接受一个Object...可变参数列表,将这些参数传递给MethodHandle所代表的方法进行调用。
MyClass obj = new MyClass();
MethodHandle methodHandle = MethodHandles.lookup().findVirtual(MyClass.class, "myMethod", MethodType.methodType(void.class, String.class));
// 使用 invokeWithArguments 方法调用方法句柄
methodHandle.invokeWithArguments(obj, "test argument");
- 使用
invokeSpecial方法,主要用于调用实例方法中的特殊方法,比如构造函数、super关键字调用的父类方法等。
MethodHandle lookup = MethodHandles.lookup();
MethodHandle superMethod = lookup.findSpecial(ParentClass.class, "parentMethod",
MethodType.methodType(void.class), ChildClass.class);
superMethod.invokeSpecial(this);//调用父类的parentMethod方法
- 使用
invokeStatic方法,用途:用于调用类的静态方法。
MethodHandle staticMethod = MethodHandles.lookup().findStatic(UtilityClass.class, "add",
MethodType.methodType(int.class, int.class, int.class));
int result = (int) staticMethod.invokeStatic(2, 3);
System.out.println("调用静态方法的结果: " + result);
- 使用unreflect系列
Field field = MyClass.class.getDeclaredField("myField");
// 假设MyClass类有一个名为myField的字段
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle getterHandle = lookup.unreflectGetter(field);//获取MyClass对象的myField的值
String fieldValue = (String) getterHandle.invoke(obj);
MethodHandle setterHandle = lookup.unreflectSetter(field);
setterHandle.invoke(obj, "new value");//设置MyClass对象的myField的值为"new value"
(3) 类型转换和参数重排序
-
使用
asType方法:-
可以使用
asType方法选择创建之前旧的MethodHandle对应的方法名,并指定参数和返回值类型。 -
如果没有这个参数和返回值类型的方法,那么会抛异常InvalidMethodTypeException
-
MethodHandle targetMethodHandle = methodHandle.asType(MethodType.methodType(void.class, int.class));
- 这在需要将
MethodHandle应用于不同类型的参数或在不同上下文中使用时非常有用。
(4) 组合 MethodHandle
-
使用
filterArguments等方法:- 可以使用
filterArguments对MethodHandle的参数进行过滤或修改。
- 可以使用
MethodHandle mh1 = MethodHandles.lookup().findStatic(Math.class, "sqrt", MethodType.methodType(double.class, double.class)); //findStatic查找Math这个类的一个静态方法叫sqrt,并且描述了返回值类型是double,参数类型也是double,然后生成对应的MethodHandle对象
MethodHandle mh2 = MethodHandles.constant(double.class, 2.0);// 创建一个表示2.0的常量
// 将第二个参数替换为常量 2.0
MethodHandle combined = MethodHandles.filterArguments(mh1, 1, mh2); //mh1是方法,1是要替换的参数的索引位置,mh2是修改后的参数,==> Math.sqrt(2.0)
- 使用
compose方法,实现将一个MethodHandle的返回值作为另一个MethodHandle的
// 获取一个返回double类型的方法句柄(这里假设是获取一个固定值的方法)
MethodHandle getValue = MethodHandles.constant(double.class, 4.0);
// 获取Math.sqrt方法的句柄
MethodHandle sqrt = MethodHandles.lookup().findStatic(Math.class, "sqrt", MethodType.methodType(double.class, double.class));
// 使用compose组合两个方法句柄
MethodHandle composed = sqrt.compose(getValue);
// 调用组合后的方法句柄,相当于先获取4.0,再对其求平方根
double result = (double) composed.invoke();
System.out.println(result);
- 使用
collectArguments将多个参数合并成一个数组作为MethodHandle的参数。
// 定义一个接受数组参数的方法句柄(假设是一个打印数组元素的方法)
MethodHandle printArray = MethodHandles.lookup().findVirtual(System.out.getClass(), "println", MethodType.methodType(void.class, Object.class));
// 创建两个参数的方法句柄
MethodHandle arg1 = MethodHandles.constant(int.class, 1);
MethodHandle arg2 = MethodHandles.constant(int.class, 2);
// 使用collectArguments将两个参数合并成数组作为printArray的参数
MethodHandle combined = MethodHandles.collectArguments(printArray, 0, MethodType.methodType(int[].class, int.class, int.class));//对printArray这个方法的参数从0索引位置开始收集参数
// 调用组合后的方法句柄,相当于打印[1, 2]
combined.invoke(arg1.invoke(), arg2.invoke());
- 使用
bindTo方法,将MethodHandle的一个参数绑定为一个固定值
// 获取一个有两个参数的方法句柄(假设是一个加法方法)
MethodHandle add = MethodHandles.lookup().findStatic(Math.class, "addExact", MethodType.methodType(int.class, int.class, int.class));
// 将第一个参数绑定为5
MethodHandle bound = add.bindTo(5);
// 调用绑定后的方法句柄,相当于计算5 + 3
int result = (int) bound.invoke(3);
System.out.println(result);
- 使用
insertArguments方法,在MethodHandle的参数列表中插入固定值。
// 获取一个有两个参数的方法句柄(假设是一个乘法方法)
MethodHandle multiply = MethodHandles.lookup().findStatic(Math.class, "multiplyExact", MethodType.methodType(int.class, int.class, int.class));
// 在第一个参数位置插入固定值2
MethodHandle inserted = MethodHandles.insertArguments(multiply, 0, 2);
// 调用插入参数后的方法句柄,相当于计算2 * 3
int result = (int) inserted.invoke(3);
System.out.println(result);
- 使用
asCollector方法,可以将多个参数收集为一个集合类型的参数(如List等),然后传递给目标方法。
MethodHandle originalMethod = MethodHandles.lookup().findVirtual(ListProcessingClass.class, "processList", MethodType.methodType(void.class, List.class));
MethodHandle collectedMethod = originalMethod.asCollector(List.class, 3);
ListProcessingClass obj = new ListProcessingClass();
collectedMethod.invoke(obj, 1, 2, 3);
(5) 异常处理
-
捕获和处理异常:
- 调用
MethodHandle时可能会抛出异常,需要使用try-catch语句进行捕获。
- 调用
try {
methodHandle.invoke(obj, "argument");
} catch (Throwable throwable) {
// 处理异常
}
(6) 案例代码
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
class MyClass {
public void myMethod(String message) {
System.out.println(message);
}
}
public class MethodHandleExample {
public static void main(String[] args) throws Throwable {
//1.查找和创建 MethodHandle:使用 MethodHandles.lookup() 找到 MyClass 的 myMethod 方法,并使用 unreflect 将其转换为 MethodHandle。
MethodHandles.Lookup lookup = MethodHandles.lookup();
// getMethod第一个参数是方法的名称,后面的参数是方法要传入的参数类型
// method ==> public void com.qihongze.dw.flow.joinDim.MyClass.myMethod(java.lang.String)
Method method = MyClass.class.getMethod("myMethod", String.class);
// unreflect是为了定义下面的invoke调用,需要传入的参数是method对象的类名和参数列表
// methodHandle ==> MethodHandle(MyClass,String)void
MethodHandle methodHandle = lookup.unreflect(method);
MyClass obj = new MyClass();
//2.调用 MethodHandle:使用 invoke 方法调用 MethodHandle,传入 MyClass 的实例和参数。
methodHandle.invoke(obj, "Hello, MethodHandle!");
//3.修改 MethodHandle 类型:使用 asType 方法将 MethodHandle 的参数类型修改为 Object.class,并使用 invoke 调用修改后的 MethodHandle。
// 下面代码会报错WrongMethodTypeException: cannot convert MethodHandle(MyClass,String)void to (Object)void 因为methodHandle的类型是MethodHandle(MyClass,String)void,然后想要转为MethodHandle(Object)void,发生类型不匹配,因为少一个参数,应该转为(Object,String)void类型
MethodHandle modifiedHandle = methodHandle.asType(MethodType.methodType(void.class, Object.class));
//正确代码
MethodHandle modifiedHandle = methodHandle.asType(MethodType.methodType(void.class, Object.class, String.class));
// 调用修改后的 MethodHandle
modifiedHandle.invoke(obj, (Object) "Hello, Modified MethodHandle!");
Field nameField = obj.getClass().getDeclaredField("name");
nameField.setAccessible(true);
// 往obj对象的name字段中调用set方法设置值为John
MethodHandle setNameMH = lookup.unreflectSetter(nameField);
setNameMH.invoke(obj, "John");
// 获取obj对象的name字段属性值
MethodHandle nameMH = lookup.unreflectGetter(nameField);
Object invoke = nameMH.invoke(obj);
System.out.println(invoke);
}
}
(7) 注意事项
- 性能开销:
- 由于
MethodHandle涉及反射,性能开销可能比直接方法调用大,因此在性能敏感的代码中要谨慎使用。 - 类型匹配:
- invoke是要看
MethodHandle是get还是set,如果是get只传一个参数;如果是set需要传多个参数,第一个参数是Field,后面都是构造函数。千万注意WrongMethodTypeException类型不匹配问题。 - 使用
invokeExact时,要确保参数类型严格匹配,否则会抛出WrongMethodTypeException。