MethodHandle与反射Method区别
在《深入理解 java 虚拟机》第二版第 8 章,上提出了一个问题,简要描述如下:
在 Son 类中,调用 GrandFather 的 thinking 方法,打印 I 'm grandFather。
Son 类,GrandFather 类定义如下:
public class MethodHandleTest {
class GrandFather{
void thinking(){
System.out.println("I 'm grandFather!");
}
}
class Father extends GrandFather{
void thinking(){
System.out.println("I 'm father!");
}
}
class Son extends Father{
void thinking(){
//实现祖父类的thinking(),打印 I 'm grandFather
}
}
}
针对这个问题,书中引出了 java.lang.invoke 包,下面简要介绍下
简介 java.lang.invoke 包
JDK1.7 之后,加入的 java.lang.invoke 包,该包提供了一种新的确定动态目标方法的机制,Method Handle.
Method Handle 使得 Java 拥有了类似函数指针或委托的方法别名的工具。
简单使用方式
- 创建目标方法的 MethodType 对象,MethodType.methodType 方法的第一个参数是返回值,之后是按目标方法接收的参数的顺序填写参数类型。
- MethodHandles.lookup () 对应的 findXXX 方法,获取目标方法的 MethodHandle 对象。
- 调用 MethodHandle 对象的 invokeExact 方法。该方法参数是目标方法的参数。
举例说明如下:
例如 我们尝试调用:System.out.println 的方法,该方法在 JDK 中的定义如下:
public void println(String x){
...
}
获取 System.out.println 的 MethodType 对象,如下
/**
* MethodType代表方法的类型,包含方法的返回值(第一个参数),之后是按顺序的方法接收的参数
* */
MethodType mt= MethodType.methodType(void.class,String.class);
获取 System.out.println 的 MethodHandle 的(这里边有一个 findVirtual 方法,是用于执行虚方法的。)方式如下:
/**
* lookup方法来自于MethodHandles.Lookup
* 用于在指定类(第一个参数),指定方法名称(第二个参数),指定方法类型(第三参数)查找
* 符合访问权限的方法句柄
* **/
/**
* 因为这里调用的是一个虚方法,
* 按照java语言的规范,第一个参数是隐式的代表该该方法的接收者,就是this,这里由bindTo方法进行处理。
* **/
MethodHandles.lookup().findVirtual(receiver.getClass(),"println",mt).bindTo(receiver);
MethodHandles.Lookup 的 findXXX 方法说明
MethodHandle 方法 | 字节码 | 描述 |
---|---|---|
findStatic | invokestatic | 调用静态方法 |
findSpecial | invokespecial | 调用实例构造方法,私有方法,父类方法。 |
findVirtual | invokevirtual | 调用所有的虚方法 |
findVirtual | invokeinterface | 调用接口方法,会在运行时再确定一个实现此接口的对象。 |
看了上边的简要说明,很自然的想法就是 MethodType 先描述下 thinking 方法,
之后使用 MethodHandles.lookup () 的 findSpecial 方法,在 GrandFather 上查找 thinking 方法进行执行。
书上的解法也类似,下面咱们就看看书上的解法。
关于引子书上的解法
public class MethodHandleTest {
class GrandFather{
void thinking(){
System.out.println("I 'm grandFather!");
}
}
class Father extends GrandFather{
void thinking(){
System.out.println("I 'm father!");
}
}
class Son extends Father{
void thinking() {
//实现祖父类的thinking(),打印 I 'm grandFather
MethodType mt=MethodType.methodType(void.class);
try {
MethodHandle md=MethodHandles.lookup().findSpecial(GrandFather.class, "thinking", mt,this.getClass());
md.invoke(this);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MethodHandleTest.Son son=new MethodHandleTest().new Son();
son.thinking();
}
}
上述代码在 JDK1.7.0_09 上运行正常,运行结果是 I'm grandFather
image.png但是 ** 该解法在 JDK1.8 下不行 **,运行结果是 I’m father
image.pngJDK1.8 为什么跟预想的不一致?
为什么 1.8 跟预想的不一致? JDK8 规范说明有阐述
本人摘录其中的一段文字说明如下:
A lookup class which needs to create method handles will call MethodHandles.lookup to create a factory for itself.
When the Lookup factory object is created, the identity of the lookup class is determined,
and securely stored in the Lookup object.
The lookup class (or its delegates) may then use factory methods on the Lookup object to create method handles
for access-checked members.
This includes all methods, constructors, and fields which are allowed to the lookup class, even private ones.
简要翻译如下:
需要创建 method handles 的查找类将调用 MethodHandles.lookup 为它自己创建一个工厂。
当该工厂对象被查找类创建后,查找类的标识,安全信息将存储在其中。
查找类 (或它的委托) 将使用工厂方法在被查找对象上依据查找类的访问限制,创建 method handles。
可创建的方法包括:查找类所有允许访问的所有方法、构造函数和字段,甚至是私有方法。
简单说就是 :JDK1.8 下 MethodHandles.lookup 是调用者敏感的,不同调用者访问权限不同,其结果也不同。
在本例中,在 Son 类中调用 MethodHandles.lookup,受到 Son 限制,仅仅能访问到 Father 类的 thinking。所以结果是:'I'm father'
在这里,各位看官,心中一定会有一个疑问:这个包与 java.lang.reflecct 包区别是什么?
与 java.lang.reflecct 包的区别
MethodHandle的使用方法和效果上与Reflection都有众多相似之处。不过,它们也有以下这些区别:
-
Reflection 在模拟 Java 代码层次的调用,而 MethodHandle 在模拟字节码层次的方法调用。Reflection和MethodHandle机制本质上都是在模拟方法调用,但是Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用。在MethodHandles.Lookup上的三个方法findStatic()、findVirtual()、findSpecial()正是为了对应于invokestatic、invokevirtual & invokeinterface和invokespecial这几条字节码指令的执行权限校验行为,而这些底层细节在使用Reflection API时是不需要关心的。
-
Reflection 是重量级,而 MethodHandle 是轻量级。Reflection中的java.lang.reflect.Method对象远比MethodHandle机制中的java.lang.invoke.MethodHandle对象所包含的信息来得多。前者是方法在Java一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含有执行权限等的运行期信息。而后者仅仅包含着与执行该方法相关的信息。用开发人员通俗的话来讲,Reflection是重量级,而MethodHandle是轻量级。
-
MethodHandle 可以进行内联优化,Reflection 完全没有。由于MethodHandle是对字节码的方法指令调用的模拟,那理论上虚拟机在这方面做的各种优化(如方法内联),在MethodHandle上也应当可以采用类似思路去支持(但目前实现还不完善)。而通过反射去调用方法则不行。
-
和反射相比好处是:
- 调用 invoke() 已经被JVM优化,类似直接调用一样。
性能好得多,类似标准的方法调用。 - 当我们创建MethodHandle 对象时,实现方法执行权限检测(如2中的MethodHandles.Lookup上的三个方法findStatic()、findVirtual()、findSpecial()),而不是调用invoke() 时。
- MethodHandle 服务于所有 java 虚拟机上的语言,Reflection 仅仅服务于 java 语言。MethodHandle与Reflection除了上面列举的区别外,最关键的一点还在于去掉前面讨论施加的前提“仅站在Java语言的角度看”之后:Reflection API的设计目标是只为Java语言服务的,而MethodHandle则设计为可服务于所有Java虚拟机之上的语言,其中也包括了Java语言而已。
、