JDK1.8下关于MethodHandle问题

Wesley13
• 阅读 545

最近在读《深入理解java虚拟机》第二版,在JDK1.8环境下遇到一个关于MethodHandle使用上的问题,在这里记录下。https://github.com/floor07/essential-jvm (github jvm的读书笔记)

本文目录如下:

  • 引子
  • java.lang.invoke简介
  • 关于引子书上的解法
  • JDK1.8为什么跟预想的不一致

引子

在《深入理解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

JDK1.8下关于MethodHandle问题

但是 **该解法在JDK1.8下不行**,运行结果是I’m father

JDK1.8下关于MethodHandle问题

JDK1.8为什么跟预想的不一致?

为什么1.8跟预想的不一致?带着这个疑问我查阅了JDK8规范说明

详细信息请查阅
https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.Lookup.html#findSpecial-java.lang.Class-java.lang.String-java.lang.invoke.MethodType-java.lang.Class  

 本人摘录其中的一段文字说明如下:  

 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服务于所有java虚拟机上的语言,Reflection仅仅服务于java语言。
  • Reflection在模拟Java代码层次的调用,而MethodHandle在模拟字节码层次的方法调用。
  • Reflection是重量级,而MethodHandle是轻量级。
  • MethodHandle可以进行内联优化,Reflection完全没有。

总结

Java一致在更新,也越来越严禁,看书时,一定要注意对比最新的官方文档。

JDK1.8环境下MethodHandles.lookup方法是调用者敏感的。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Wesley13 Wesley13
2年前
P2P技术揭秘.P2P网络技术原理与典型系统开发
Modular.Java(2009.06)\.Craig.Walls.文字版.pdf:http://www.t00y.com/file/59501950(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.t00y.com%2Ffile%2F59501950)\More.E
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Flink SQL Window源码全解析
!(https://oscimg.oschina.net/oscnet/72793fbade36fc18d649681ebaeee4cdf00.jpg)(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzU3MzgwNT
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
京东云开发者 京东云开发者
7个月前
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这