【Java基础】反射-invoke包

7 篇文章 0 订阅

什么是MethodHandle?

从Java 7开始提供了另一套API MethodHandle 。其与反射的作用类似,可以在运行时访问类型信息,但是据说其执行效率比反射更高,也被称为Java的 现代化反射。

官方对其定义如下:

A method handle is a typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values.

如何通过进行反射?

通过方法句柄来访问一下某个类里面的方法以及属性等,总的来说只需要4步:

  1. 创建Lookup
  2. 创建MethodType
  3. 基于Lookup与MethodType获得MethodHandle
  4. 调用MethodHandle

创建一个反射的目标类用于后面的例子

public class Hello {

    private String name;

    public void hello(String name){
        System.out.println("hello "+name);
    }

    public void say(String name){
        System.out.println("say "+name);
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}
  1. 创建Lookup

    MethodHandles是MethodHandle的工厂类,它提供了一系列静态方法用于获取MethodHandle。使用如下代码创建一个lookup,以这种方式得到的lookup很强大,凡是调用类支持的字节码操作,它都支持。

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    

    还可以使用publicLookup方法创建,但是以此种方式创建的lookup只能访问类中public的成员。

    MethodHandles.Lookup publicLookup=MethodHandles.publicLookup();
    
  2. 创建MethodType

    第一个参数是方法的返回类型,第二参数开始为方法的形式参数类型(例子中只有一个String类型参数,实际可以有多个)

    MethodType.methodType(Void.class, String.class)
    
  3. 基于Lookup与MethodType创建MethodHandle

    MethodHandle helloMethod = lookup.findVirtual(Hello.class, "hello", MethodType.methodType(Void.class, String.class));
    
  4. 通过MethodHandle传入对象实例targetInstance和方法参数字符串“MethodHandle”调用对象方法hello

    Hello targetInstance = new Hello();
    helloMethodHandle.invoke(helloInstance,"MethodHandle");
    
  5. 通过MethodHandle传入对象实例helloInstance和属性名称获取属性值

    //访问属性,需要借助反射得到Field,如果改属性是private的,还需要设置Field的setAccessible为true
    Field nameField = Hello.class.getDeclaredField("name");
    nameField.setAccessible(true);
    MethodHandle nameHandle = lookup.unreflectGetter(nameField);
    System.out.println((String) nameHandle.invoke(targetInstance));
    
    

反射API

创建MethodHandle

主要通过lookup里面的以下方法来创建MethodHandle

创建构造函数MethodHandle

public MethodHandle findConstructor(Class<?> refc, MethodType type) 

refc: 要检索的类
type: 对应的构造函数的MethodType

创建实例方法MethodHandle

public MethodHandle findVirtual(Class<?> refc, String name, MethodType type)

name: 方法名称

创建类方法的MethodHandle

public   MethodHandle findStatic(Class<?> refc, String name, MethodType type)

创建非private的Field的访问MethodHandle 。注意这个不是获取field的javabean Setter方法,与其毫无关系。通过这个setter 方法句柄我们就可以访问到这个属性了。

public MethodHandle findGetter(Class<?> refc, String name, Class<?> type)

对应的如果要设置此属性的值,使用Setter方法句柄

public MethodHandle findSetter(Class<?> refc, String name, Class<?> type)

通过MethodHandle调用目标方法

public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;

结语

  1. Java 9提供了增强的VarHandle,VarHandle主要用于动态操作数组的元素或对象的成员变量。VarHandle与MethodHandle非常类似,它也需要通过MethodHandles来获取实例,接下来调用VarHandle的方法即可动态操作指定数组的元素或指定对象的成员变量。
  2. 上文可知MethodHandle(方法句柄)访问目标对象属性值的时候需要借助反射的API。回忆一下Java反射一文中的结语,“但如果在Java 9或更高的版本中使用这种方式会看见这样的警告。而Java 16开始默认拒绝非法访问,于是会直接报错”。所以refection和invoke包的这两种方式都不是完全反射,java还提供了一种完全反射的方法,即通过UnSafe,下文详解。
Java中的反射机制允许运行时获取类的信息以及操作对象实例,而`reflect.invoke()`是一个通过反射直接调用目标方法的功能。 ### Java Reflect 的 `invoke()` 方法简介 `invoke()` 方法是 `java.lang.reflect.Method` 类的一个静态方法,在 `Method` 对象上调用此方法可以动态地调用该方法所代表的类或接口的方法。这个功能非常强大,可以在运行时决定要调用的方法名称、参数及其返回值类型,并传递给实际的对象。 #### 参数说明: 1. **第一个参数** - 要调用方法的实例对象 (`Object instance`)。 2. **第二个参数** - 方法所需的参数列表 (`Object[] args`)。数组中的每个元素对应于方法声明中的一个参数。 3. **第三个参数** - 可选的标志位,用于控制如何处理方法调用的异常 (`boolean throwException`)。默认为 `false` 表示忽略异常并抛出 `IllegalAccessException`;设置为 `true` 则会抛出任何捕获到的异常。 ### 使用例子 假设我们有一个简单的类 `ExampleClass`: ```java public class ExampleClass { public void exampleMethod(int x) { System.out.println("Received value: " + x); } } ``` 然后我们需要通过反射调用其 `exampleMethod` 方法: ```java import java.lang.reflect.Method; class ReflectionDemo { public static void main(String[] args) throws Exception { Class<?> clazz = ExampleClass.class; Method method = clazz.getMethod("exampleMethod", int.class); // 创建 ExampleClass 的实例 ExampleClass exampleInstance = new ExampleClass(); // 获取 Method 实例并调用它 Object result = method.invoke(exampleInstance, 42); // 输出结果 (尽管实际上这里的结果并没有被存储在变量里) System.out.println("Invoke result is: " + result); } } ``` 在这个例子中,`getMethod("exampleMethod", int.class)` 获取了 `exampleMethod` 方法的 `Method` 实例,并通过 `invoke` 方法调用了它,传入了一个整数作为参数。 ### 相关问题: 1. **反射和运行时灵活性之间的关系是什么?** 反射提供了一种在运行时查看和操纵类信息的能力,这极大地增加了程序的运行时灵活性,括动态加载类、检查类属性和方法等。 2. **何时不应该使用反射?** 尽管反射功能强大,但在需要高效性能的场合下(如大规模数据处理或高并发应用),过度使用反射可能会导致性能下降。反射涉及到对字节码的解析和解释过程,相比于直接的指令执行,它的效率较低。 3. **如何优化使用反射以提高性能?** 尽量避免频繁使用反射,特别是在循环内或者需要大量调用的地方。考虑将反射的操作封装在初始化阶段完成,例如在构造函数或静态初始化块中预先获取所需的信息,而不是每次方法调用时都依赖反射。同时,使用原始类型而非装类可以减少不必要的创建和销毁操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值