java 核心技术-12版 卷Ⅰ- 5.9.7 调用任意方法和构造器

原文

5.9.7 调用任意方法和构造器

在C和C++ 中,可以通过一个函数指针执行任意函数。从表面上看,Java 没有提供方法指针,也就是说,Java 没有提供途径将一个方法的存储地址传给另外一个方法,以便第二个方法以后调用。事实上,Java 的设计者曾说过: 方法指针很危险,而且很容易出错。他们认为Java 的接口 (interface) 和 lambda 表达式(将在下一章讨论) 是一种更好的解方案。不过,反射机制允许你调用任意的方法。

回想一下,可以用 Field类的 get 方法查看一个对象的字段。与之类似,Method 类有一个invoke 方法,允许你调用包装在当前 Method 对象中的方法。invoke 方法的签名为Object invoke(0bject obj, Object... args)

第一个参数是隐式参数,其余的对象提供了显式参数。

对于静态方法,第一个参数会忽略,即可以将它设置为 null。例如,假设用 m1 表示 Employee 类的 getName 方法,下面这条语句显示了如何调用这个方法:

String n = (String) m1.invoke(harry);

如果返回类型是基本类型,则 invoke 方法会返回其包装器类型。例如,假设 m2 表示Employee 类的 getSalary 方法,那么返回的对象实际上是一个 Double,必须相应地完成强制类型转换。可以使用自动拆箱将它转换为一个 double:

double s = (Double) m2.invoke(harry);

如何得到 Method 对象呢?当然,可以调用 getDeclaredMethods 方法,然后搜索返回的 Method对象数组,直到发现想要的方法为止。也可以调用 Class 类的 getMethod 方法。这与 getField 方去类似。getField 方法接受一个表示字段名的字符申,返回一个 Field 对象。不过,有可能存在若干个同名的方法,因此要准确地得到想要的那个方法必须格外小心。有鉴于此,还必须是供想要的方法的参数类型。getMethod 的签名为

Method getMethod(String name, Class... parameterTypes)

例如,下面展示了如何获得 Employee 类的 getName 方法和 raiseSalary 方法的方法指针:

Method m1 = Employee.class.getMethod("getName");

Method m2 = Employee.class.getMethod("raiseSalary", double.class);

可以使用类似的方法调用任意的构造器。将构造器的参数类型提供给 Class.getConstructor方法,并为 Constructor.newInstance 方法提供参数值;


// or any other class with a constructor that

// accepts a long parameter

Class cl = Random.class;

Constructor cons = cl.getConstructor(long.class);

Object obj = cons.newInstance(42L);

注释: Method和 Constructor 类扩展了 Executable 类。在Java 17中,Executable 类是密封类,只允许Method和 Constructor 作为子类。

到此为止,我们已经了解了使用 Method 对象的规则。下面来看如何具体使用。程序清单 5-19的程序会打印一个数学函数 (如 Math.sqrt 或 Math.sin) 的取值表。打印的结果如下所示:

public static native double java.lang.Math.sqrt(double)

1.0000 | 1.0000

2.0000 | 1.4142

3.0000 | 1.7321

4.0000 | 2.0000

5.0000 | 2.2361

6.0000 | 2.4495

7.0000 | 2.6458

8.0000 | 2.8284

9.0000 | 3.0000

10.0000 | 3.1623

当然,打印表格的代码与表格中计算的数学函数无关


double dx = (to - from ) / (n -1);
for( double x = from;x <= to; x+= dx){
	double y = (Double) f.invoke(null, x);
  System.out.println("%10.4f | %10.4f%n",x ,x);
}

在这里,f 是一个 Method 类型的对象。由于我们调用的方法是一个静态方法,所以 invoke的第一个参数是 null。

要打印 Math.sqrt 函数的取值表,可以如下设置 f:

Math.class.getMethod("sqrt",double.class)

这是 Math类的一个方法,名为 sqrt,有一个 double 类型的参数。程序清单 5-19 给出了这个通用取值表程序和两个测试的完整代码

程序清单5-19
methods/MethodTableTest.java

package methods;

import java.lang.reflect.*;

/**
 * This program shows how to invoke methods through reflection.
 * @version 1.2 2012-05-04
 * @author Cay Horstmann
 */
public class MethodTableTest
{
   public static void main(String[] args)
         throws ReflectiveOperationException
   {
      // get method pointers to the square and sqrt methods
      Method square = MethodTableTest.class.getMethod("square", double.class);
      Method sqrt = Math.class.getMethod("sqrt", double.class);

      // print tables of x- and y-values
      printTable(1, 10, 10, square);
      printTable(1, 10, 10, sqrt);
   }

   /**
    * Returns the square of a number
    * @param x a number
    * @return x squared
    */
   public static double square(double x)
   {
      return x * x;
   }

   /**
    * Prints a table with x- and y-values for a method
    * @param from the lower bound for the x-values
    * @param to the upper bound for the x-values
    * @param n the number of rows in the table
    * @param f a method with a double parameter and double return value
    */
   public static void printTable(double from, double to, int n, Method f)
         throws ReflectiveOperationException
   {
      // print out the method as table header
      System.out.println(f);

      double dx = (to - from) / (n - 1);

      for (double x = from; x <= to; x += dx)
      {
         double y = (Double) f.invoke(null, x);
         System.out.printf("%10.4f | %10.4f%n", x, y);
      }
   }
}

这个例子清楚地表明,利用 Method 对象可以实现 C 语言中函数指针(或C# 中的委托)所能完成的所有操作。同C 中一样,这种编程风格不是很方便,而且总是很容易出错。如果在调用方法的时候提供了错误的参数会发生什么? invoke 方法将会抛出一个异常。

另外,invoke 的参数和返回值必须是 Object 类型。这就意味着必须来回进行多次强制类型转换。这样一来,编译器会丧失检查代码的机会,以至于等到测试阶段才会发现错误,而这个时候查找和修正错误会麻烦得多。不仅如此,使用反射获得方法指针的代码要比直接调用方法的代码慢得多。

有鉴于此,建议仅在绝对必要的时候才在你自己的程序中使用 Method 对象。通常,更好的做法是使用接口以及Java 8引人的 lambda 表达式(第6章中介绍)。特别要强调:我们建议Java 开发人员不要使用回调函数的 Method 对象。可以使用回调的接口,这样不仅代码的执行速度更快,也更易于维护。

java.lang.reflect.Method 1.1

  • public Object invoke(Object implicitParameter, Object[] explicitParameters) 调用这个对象描述的方法,传人给定参数,并返回那个方法的返回值。对于静态方法,传人 null作为隐式参数。使用包装器传递基本类型值。基本类型的返回值必须拆包。

相关文章

java 方法定义与调用、以及调试技巧

一、方法定义与调用1.1 方法基础public class MethodDemo { // 无返回值方法 public static void sayHello() {...

如何使用Java的反射机制以及调用构造方法?

关于如何使用Java的反射机制,兆隆IT云学院的java高级实用技术的课本中是这样的描述的:1、获取想要操作类的java.lang.Class对象。每个类被加载后,系统就会为该类生成一个对应的Clas...

字节架构师:来说说Java异步调用的几种方式你都搞懂了吗?

日常开发中,会经常遇到说,前台调服务,然后触发一个比较耗时的异步服务,且不用等异步任务的处理结果就对原服务进行返回。这里就涉及的Java异步调用的一个知识。下面本文尝试将Java异步调用的多种方式进行...

java 服务之间调用(rpc)

RPC 是一种技术思想而非一种规范或协议,常见 RPC 技术和框架有:应用级的服务框架: Dubbo、Google gRPC、Spring Boot/Spring Cloud。远程通信协议:RMI、S...

Java中实现接口的三种方式您造吗?

本文介绍了Java中实现接口的三种方式:常规实现方式、匿名内部类和 Lambda表达式实现方式。希望已经了解的同学可以重新温习一下,不了解的同学则从中受益!Java中接口最常规的实现方式同学们都会知道...

java实现调用http请求的几种常见方式

欢迎大家关注我的公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。一、概述在实际开发过程中,我们经常需要调用对方提供的接口或测试自己写的...