Java 反射 Method的invoke回调调用任意方法

@author ixenos

关键子:Method、Field、invoke方法指针/函数指针、回调函数

invoke回调流程示例


0.由Class对象动态构造对应类型对象

1.Class对象的getMethod方法,由方法名和形参构造Method对象

2.Method对象的invoke方法来委托动态构造的对应类型对象,使其执行对应形参的add方法,这是回调函数(方法)的功能

“回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。”

//动态构造InvokeTest类的实例
Class<?> classType = InvokeTest.class;
Object invokeTest = classType.newInstance(); //动态构造InvokeTest类的add(int num1, int num2)方法,标记为addMethod的Method对象
Method addMethod = classType.getMethod("add", new Class[]{int.class, int.class}); //动态构造的Method对象invoke委托动态构造的InvokeTest对象,执行对应形参的add方法
Object result = addMethod.invoke(invokeTest, new Object[]{1, 2}); //测试输出
System.out.println((Integer)result);
public Method getMethod(String name,
Class<?>... parameterTypes)
throws NoSuchMethodException,
SecurityException返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。name 参数是一个 String,用于指定所需方法的简称。parameterTypes 参数是按声明顺序标识该方法形参类型的 Class 对象的一个数组。如果 parameterTypes 为 null,则按空数组处理。
如果 name 是 "<init>;" 或 "<clinit>",则将引发 NoSuchMethodException。否则,要反映的方法由下面的算法确定(设 C 为此对象所表示的类): 在 C 中搜索任一匹配的方法。如果找不到匹配的方法,则将在 C 的超类上递归调用第 1 步算法。
如果在第 1 步中没有找到任何方法,则在 C 的超接口中搜索匹配的方法。如果找到了这样的方法,则反映该方法。
在 C 类中查找匹配的方法:如果 C 正好声明了一个具有指定名称的公共方法并且恰恰有相同的形参类型,则它就是反映的方法。如果在 C 中找到了多个这样的方法,并且其中有一个方法的返回类型比其他方法的返回类型都特殊,则反映该方法;否则将从中任选一个方法。
注意,类中可以有多个匹配方法,因为尽管 Java 语言禁止类声明带有相同签名但不同返回类型的多个方法,但 Java 虚拟机并不禁止。这增加了虚拟机的灵活性,可以用来实现各种语言特性。例如,可以使用桥方法 (brige method)实现协变返回;桥方法以及将被重写的方法将具有相同的签名,不同的返回类型。 请参阅Java 语言规范 第 8.2 和 8.4 节。 参数:
name - 方法名
parameterTypes - 参数列表
返回:
与指定的 name 和 parameterTypes 匹配的 Method 对象
抛出:
NoSuchMethodException - 如果找不到匹配的方法,或者方法名为 "<init>" 或 "<clinit>"
NullPointerException - 如果 name 为 null
SecurityException - 如果存在安全管理器 s,并满足下列任一条件:
调用 s.checkMemberAccess(this, Member.PUBLIC) 拒绝访问方法
调用者的类加载器不同于也不是当前类的类加载器的一个祖先,并且对 s.checkPackageAccess() 的调用拒绝访问该类的包

API-Class-getMethod

public Object invoke(Object obj,
Object... args)
throws IllegalAccessException,
IllegalArgumentException,
InvocationTargetException对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。
如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 如果底层方法是实例方法,则使用动态方法查找来调用它,这一点记录在 Java Language Specification, Second Edition 的第 15.12.4.4 节中;在发生基于目标对象的运行时类型的重写时更应该这样做。 如果底层方法是静态的,并且尚未初始化声明此方法的类,则会将其初始化。 如果方法正常完成,则将该方法返回的值返回给调用者;如果该值为基本类型,则首先适当地将其包装在对象中。但是,如果该值的类型为一组基本类型,则数组元素不 被包装在对象中;换句话说,将返回基本类型的数组。如果底层方法返回类型为 void,则该调用返回 null。 参数:
obj - 从中调用底层方法的对象
args - 用于方法调用的参数
返回:
使用参数 args 在 obj 上指派该对象所表示方法的结果
抛出:
IllegalAccessException - 如果此 Method 对象强制执行 Java 语言访问控制,并且底层方法是不可访问的。
IllegalArgumentException - 如果该方法是实例方法,且指定对象参数不是声明底层方法的类或接口(或其中的子类或实现程序)的实例;如果实参和形参的数量不相同;如果基本参数的解包转换失败;如果在解包后,无法通过方法调用转换将参数值转换为相应的形参类型。
InvocationTargetException - 如果底层方法抛出异常。
NullPointerException - 如果指定对象为 null,且该方法是一个实例方法。
ExceptionInInitializerError - 如果由此方法引起的初始化失败。

API-Method-invoke

用反射实现获得运行时对象的拷贝的方法(Field类)


 import java.lang.class;
import java.lang.reflect; public class ReflectTester
{
  public Object copy(Object object) throws Exception
  {
    Class<?> classType = object.getClass();
    Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
    //获得对象的所有成员变量
    Field[] fields = classType.getDeclaredFields();
    for(Field field : fields)
    {
      String name = field.getName();
      //将属性的首字母转换为大写
      String firstLetter = name.substring(0,1).toUpperCase();
      //substring(1)表示从index=1开始到最后的子字符串
      String getMethodName = "get" + firstLetter + name.substring(1);
      String setMethodName = "set" + firstLetter + name.substring(1);
      Method getMethod = classType.getMethod(getMethodName, new Class[] {});
      Method setMethod = classType.setMethod(getMethodName, new Class[] { field.getType() });
      Object value = getMethod.invoke(object, new Object[] {});
      setMethod.invoke(objectCopy, new Object[] { value });
    }
    return objectCopy;
  }
  public static void main(String[] args)
  {
    Customer customer = new Customer("Tom", 20);
    customer.setId(1L);     ReflectTester test = new ReflectTester();
    Customer customer2 = (Customer) test.copy(customer);
    System.out.println(customer2.getId() + "," + customer2.getName() + "," + customer2.getAge());
  }
} Class Customer
{
  private float ID;
  private String name;
  private int age;
  public Customer()
  {
    this.ID = 1L;
    this.name = A;
    this.age = 1;
  }
  public Customer(String name, int age)
  {
    this();
    this.name = name;
    this.age = age;
  }
  public void setID(float ID)
  {
    this.ID = ID;
  }
}

0.利用Class对象得到对应类型对象

1.利用Class对象的getDeclaredFields方法,得Field[]数组

2.遍历Field[]数组,构造每一个属性名的字符串,使首字母大写,再连接get字符串,组成setter和getter的命名

3.遍历的同时,getMethod,参数便是要构建的方法名和形参类型,得到各属性的setter和getter方法的Method对象

4.同时,得到方法后,调用各Method对象的invoke委托对应类型对象执行方法(有实参传入,或返回值)

Method的invoke方法能调用任意方法


Method类中的invoke方法,允许调用包装在Method对象中的方法

Object invoke(Object obj, Object... args) //对应隐式参数和显式参数
public Object invoke(Object obj,
Object... args)
throws IllegalAccessException,
IllegalArgumentException,
InvocationTargetException对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。
如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 如果底层方法是实例方法,则使用动态方法查找来调用它,这一点记录在 Java Language Specification, Second Edition 的第 15.12.4.4 节中;在发生基于目标对象的运行时类型的重写时更应该这样做。 如果底层方法是静态的,并且尚未初始化声明此方法的类,则会将其初始化。 如果方法正常完成,则将该方法返回的值返回给调用者;如果该值为基本类型,则首先适当地将其包装在对象中。但是,如果该值的类型为一组基本类型,则数组元素不 被包装在对象中;换句话说,将返回基本类型的数组。如果底层方法返回类型为 void,则该调用返回 null。 参数:
obj - 从中调用底层方法的对象
args - 用于方法调用的参数
返回:
使用参数 args 在 obj 上指派该对象所表示方法的结果
抛出:
IllegalAccessException - 如果此 Method 对象强制执行 Java 语言访问控制,并且底层方法是不可访问的。
IllegalArgumentException - 如果该方法是实例方法,且指定对象参数不是声明底层方法的类或接口(或其中的子类或实现程序)的实例;如果实参和形参的数量不相同;如果基本参数的解包转换失败;如果在解包后,无法通过方法调用转换将参数值转换为相应的形参类型。
InvocationTargetException - 如果底层方法抛出异常。
NullPointerException - 如果指定对象为 null,且该方法是一个实例方法。
ExceptionInInitializerError - 如果由此方法引起的初始化失败。

API-Method-invoke

0.如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null;如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null

1.第一个参数是隐式参数(比如this,传进你要委托的对象);其余的可变参数提供了显式参数(Java SE 5.0以前没有可变参数,必须传递对象数组或者null)

对于静态方法(属于类),第一个参数设置为null,作为隐式参数来传递

2.如果有返回类型,invoke方法将返回一个名义上的Object类型,实际类型由方法内部决定,所以还要进行强制类型转换

而因此如果返回类型是基本类型,为了统一返回类型,将会返回其包装器类型:

double s = (Double)m2.invoke(harry) //省略显式参数,因为没有形参,不需要实参传入

"个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。"  --JDK API 1.6 中文版

invoke的缺点


1.invoke的参数和返回值必需时Object类型的,这意味着必须进行多次的类型转换(特别是基本数据类型),而这将导致编译器错过检查代码的机会,有类型安全的风险,只有到了测试阶段才会发现这些错误,此时找到并改正他们将会更加困难

2.使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些

3.因此仅在必要时才使用Method对象,而最好使用接口和内部类,不建议Java开发者使用Method对象的回调功能,使用接口进行回调不仅会使代码的运行速度更快,还更易于维护

Java 反射 Method的invoke回调调用任意方法的更多相关文章

  1. java反射机制获取自定义注解值和方法

    由于工作需求要应用到java反射机制,就做了一下功能demo想到这些就做了一下记录 这个demo目的是实现动态获取到定时器的方法好注解名称,废话不多说了直接上源码 1.首先需要自定义注解类 /** * ...

  2. java反射-Method中的invoke方法的用法-以及函数式接口和lambda表达式

    作者最近研究框架底层代码过程中感觉自己基础不太牢固,于是写了一点案例,以防日后忘记 接口类:Animals 1 public interface Animals { 2 3 public void e ...

  3. java 反射机制之 getDeclaredMethod()获取方法,然后invoke执行实例对应的方法

    关于反射中getDeclaredMethod().invoke()的学习,来源于项目中的一行代码: SubjectService.class.getDeclaredMethod(autoMatchCo ...

  4. java反射与多态(父类调用子类)的代码演示

    package Test0817; import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method ...

  5. Java 反射 Method threw 'java.lang.InstantiationException' exception.

    查看这个InstantiationException:异常的api所说的是: 当应用程序试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该 ...

  6. Java反射定义、获取Class三种方法

    反射机制的定义: 在运行状态时(动态的),对于任意一个类,都能够得到这个类的所有属性和方法.  对于任意一个对象,都能够调用它的任意属性和方法. Class类是反射机制的起源,我们得到Class类对象 ...

  7. Java里一个线程两次调用start()方法会出现什么情况

    Java的线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start被认为是编程错误. 如果业务需要线程run中的代码再 ...

  8. JFinal提示:java.lang.RuntimeException: dao 只允许调用查询方法

    public class UserModel extends Model<UserModel>{ public static final UserModel userDao = new U ...

  9. AJAX回调(调用后台方法返回数据)

    记得先要导入jquery.js. 格式一 $.ajax({"Key1":"value1","key2":"value2" ...

随机推荐

  1. python 数据清洗之字符串处理

    在数据分析中,特别是文本分析中,字符处理需要耗费极大的精力, 因而了解字符处理对于数据分析而言,也是一项很重要的能力. 字符串处理方法 首先我们先了解下都有哪些基础方法 首先我们了解下字符串的拆分sp ...

  2. vue+webpack构建项目

    概述 -- 项目中会用到的插件 vue-router vue-resource 打包工具 webpack 依赖环境 node.js start 安装vue开发的模板 # 全局安装 vue-cli $ ...

  3. New : HTML5 中的新标签

    基础 标签 描述 <!DOCTYPE>  定义文档类型. <html> 定义 HTML 文档. <title> 定义文档的标题. <body> 定义文档 ...

  4. 关于Kafka使用IBM Java报错解决方案

    安装环境 Ubuntu 14.04 Java IBM Java 1.7.0_79 Kakfa 2.10-0.8.2.1 使用bin/kafka-server-start.sh config/serve ...

  5. WPS Office 二次开发简易教程。

    http://bbs.wps.cn/forum.php?mod=viewthread&tid=22004642

  6. Scala Singleton对象

    Scala Object: scala没有静态的修饰符,例如Java中的static.但是Scala提供了Object类型,object下的成员都是静态的,比较像Java的静态类.不同在于Scala的 ...

  7. ES 6 : 变量的解构赋值

    1. 数组的解构赋值 [ 基本用法 ] 按照一定的模式从数组或者对象中取值,对变量进行赋值的过程称为解构. 以前,为变量赋值只能直接指定值: 而ES 6 允许写成下面这样: 上面的代码表示,可以从数组 ...

  8. Android Studio新手

    目标:Android Studio新手–>下载安装配置–>零基础入门–>基本使用–>调试技能–>构建项目基础–>使用AS应对常规应用开发 AS简介 经过2年时间的研 ...

  9. #大数加减乘除#校赛D题solve

    #include<iostream> #include<cstdio> #include<cstring> #include<string> #incl ...

  10. sealed的作用

    sealed 修饰符表示密封 用于类时,表示该类不能再被继承,不能和 abstract 同时使用,因为这两个修饰符在含义上互相排斥 用于方法和属性时,表示该方法或属性不能再被重写,必须和 overri ...