文档章节

JDK1.8下关于MethodHandle问题

温安适
 温安适
发布于 2017/09/11 12:47
字数 1366
阅读 747
收藏 34
点赞 1
评论 4
JVM

最近在读《深入理解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下不行**,运行结果是I’m father

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方法是调用者敏感的。

© 著作权归作者所有

共有 人打赏支持
温安适
粉丝 93
博文 21
码字总数 35287
作品 0
朝阳
后端工程师
加载中

评论(4)

温安适
温安适

引用来自“夜苍狼”的评论

我觉得既然Java本身没有定义委托这种概念,就不要强行模仿其它语言,

这个API,是为其他运行在jvm上的其他语言使用的,单独对JAVA确实不如reflect包
夜苍狼
夜苍狼
我觉得既然Java本身没有定义委托这种概念,就不要强行模仿其它语言,
温安适
温安适

引用来自“cs6641468”的评论

干货!
结合一下R大的说法(https://www.zhihu.com/question/40427344):
MethodHandle做的事,就是模拟并实现Java字节码里的一些指令比如: invokespecial, invokevirtual, invokestatic 等。
所以, findSpecial->invoke 也要遵守JVM字节码里invokespecial指令的限制,也即R大原话: "它只能调用到传给findSpecial()方法的最后一个参数(“specialCaller”)的直接父类的版本。"
至于为何JAVA7某些版本可以越过直接父类限制而直接调用到GrantFather的方法,同样可以引用R大原话:
"可能是因为findSpecial()得到的MethodHandle的具体语义在JSR 292的设计过程中有被调整过。有一段时间findSpecial()得到的MethodHandle确实可以超越invokespecial的限制去调用到任意版本的虚方法,但这种行为很快就被认为是bug而修正了。"

感谢进一步分享
c
cs6641468
干货!
结合一下R大的说法(https://www.zhihu.com/question/40427344):
MethodHandle做的事,就是模拟并实现Java字节码里的一些指令比如: invokespecial, invokevirtual, invokestatic 等。
所以, findSpecial->invoke 也要遵守JVM字节码里invokespecial指令的限制,也即R大原话: "它只能调用到传给findSpecial()方法的最后一个参数(“specialCaller”)的直接父类的版本。"
至于为何JAVA7某些版本可以越过直接父类限制而直接调用到GrantFather的方法,同样可以引用R大原话:
"可能是因为findSpecial()得到的MethodHandle的具体语义在JSR 292的设计过程中有被调整过。有一段时间findSpecial()得到的MethodHandle确实可以超越invokespecial的限制去调用到任意版本的虚方法,但这种行为很快就被认为是bug而修正了。"

解析JDK 7的动态类型语言支持

Java虚拟机的字节码指令集的数量自从Sun公司的第一款Java虚拟机问世至JDK 7来临之前的十余年时间里,一直没有发生任何变化[1]。随着JDK 7的发布,字节码指令集终于迎来了第一位新成员——inv...

摆渡者 ⋅ 2015/12/03 ⋅ 2

java反射为什么慢

http://rednaxelafx.iteye.com/blog/548536 根据这篇文章总结,每一个Method都有一个root,不暴漏给外部,而是每次copy一个Method。具体的反射调用逻辑是委托给MethodAccessor的,而accesso...

281165273 ⋅ 2014/01/16 ⋅ 0

一台电脑如何配置多个JDK

一、起因 之前电脑装的JDK是1.7版本,由于业务需要,需要安装JDK1.8版本。可我又不想把之前的JDK1.7卸载掉。于是试着两个版本共存。 二、探索 众所周知,想要查看JDK是否安装配置成功,需要再...

qq_26545305 ⋅ 2017/03/26 ⋅ 0

NoClass:org/apache/juli/logging/LogFactory

SpringMvc+Spring+Mybatis,内嵌Jetty服务器,jdk1.8 main方法如下: 运行后报错: java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactor 按提示加上相应jar包: 再次运行又报...

liuhuics10 ⋅ 2016/08/02 ⋅ 0

在windows上实现java6和Java8共存解决办法

问题背景 公司项目中应用到的jdk环境为1.6,最近在家学习IntelliJ IDEA中sdk多环境配置时,想安装Jdk1.8,作为学习基础。那么问题来了,公司项目扩展不支持jdk1.8,为了既能满足公司项目开发环...

ruanjun ⋅ 01/14 ⋅ 0

搜索问题ava.lang.LinkageError: loader constraint violation: when resolving method

... 29 more Caused by: java.lang.LinkageError: loader constraint violation: when resolving method "java.lang.invoke.MethodHandle.invokeExact()Lorg/apache/lucene/util/AttributeIm......

outman_722324 ⋅ 2014/08/25 ⋅ 3

str.split("")在JDK1.7和JDK1.8中的区别

问题描述: 今天帮同门作华为的笔试题,我在自己电脑上调试好的代码上传后会报数组越界的异常,这让我非常的难受,经过认真查找问题的原因发现,我本地用的是JDK1.7,而上传后用的是JDK1.8编...

g_66_hero ⋅ 03/28 ⋅ 0

Java1986-1992的坑

最近项目遇到一个稀奇的事情,有一个用户的出生日期保存之后回显就会比保存的值少一天,我们最开始以为是前端解析的问题,后面意识到可能是JSON解析器的问题,先后尝试了Gson、JackSon、fas...

DivenYang ⋅ 2017/12/21 ⋅ 0

Embed Tomcat SSL报错:Invalid character found in method name. HTTP method names must be tokens

JDK1.8 embed tomcat版本:8.5.4 spring.keystore 使用的是JDK1.8的keytool生成的(keytool -genkeypair -keystore spring.keystore) 代码如下: package com.pp.ws.server; import java.io......

西夏一品堂 ⋅ 2016/09/18 ⋅ 4

java核心技术第二版使用Methodhandle实现祖类方法调用的案例中结果不正确

读java核心技术第二版,使用MethodHandle实现祖类方法调用, 代码: public class GetSuperFatherTest { } 返回值: i am father 运行环境: jdk1.7.0_80 并没有按照理想中返回 : i am gra...

llk102b ⋅ 2015/09/11 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

用SQL命令查看Mysql数据库大小

要想知道每个数据库的大小的话,步骤如下: 1、进入information_schema 数据库(存放了其他的数据库的信息) use information_schema; 2、查询所有数据的大小: select concat(round(sum(da...

源哥L ⋅ 45分钟前 ⋅ 0

两个小实验简单介绍@Scope("prototype")

实验一 首先有如下代码(其中@RestController的作用相当于@Controller+@Responsebody,可忽略) @RestController//@Scope("prototype")public class TestController { @RequestMap...

kalnkaya ⋅ 50分钟前 ⋅ 0

php-fpm的pool&php-fpm慢执行日志&open_basedir&php-fpm进程管理

12.21 php-fpm的pool pool是PHP-fpm的资源池,如果多个站点共用一个pool,则可能造成资源池中的资源耗尽,最终访问网站时出现502。 为了解决上述问题,我们可以配置多个pool,不同的站点使用...

影夜Linux ⋅ 59分钟前 ⋅ 0

微服务 WildFly Swarm 管理

Expose Application Metrics and Information 要公开关于我们的微服务的有用信息,我们需要做的就是将监视器模块添加到我们的pom.xml中: 这将使在管理和监视功能得到实现。从监控角度来看,...

woshixin ⋅ 59分钟前 ⋅ 0

java连接 mongo伪集群部署遇到的坑

部署mongo伪集群 #创建mongo数据存放文件地址mkdir -p /usr/local/config1/datamkdir -p /usr/local/config2/data mkdir -p /usr/local/config3/data mkdir -p /usr/local/config1/l......

努力爬坑人 ⋅ 今天 ⋅ 0

React Native & Weex 区别

JS引擎 Weex使用V8, React native使用JSCore JS开发框架 ( Js Framework ) Weex基于vue.js(2W+ star)。小巧轻量的前端开发框架,组件化,数据绑定,2.0引入virtual dom。 ReactNative使用...

东东笔记 ⋅ 今天 ⋅ 1

UIkit 分页组件动态加载简单实现

1. 问题描述 使用过UIkit分页组件的都清楚,UIkit的分页不能动态刷新数据,也就是不能在点击下一页的时候,动态从后台加载数据,并且刷新页数以及该页数上的数据,下面是一个简单实现,没有做...

影狼 ⋅ 今天 ⋅ 0

Mobx入门之三:Provider && inject

上一节中<App/>组件传递状态temperatures给children -- <TemperatureInput />,如果组建是一个tree, 那么属性的传递则会非常繁琐。redux使用Provider给子组件提供store, connect将子组件和s...

pengqinmm ⋅ 今天 ⋅ 0

魔兽世界 7.0版本 S23/S24/S25全职业普通+精锐套

  死亡骑士   (联盟)   (部落)   (精锐)   恶魔猎手   (联盟)   (部落)   (精锐)   德鲁伊   (联盟)   (部落)   (精锐)   猎人   (联盟) ...

wangchen1999 ⋅ 今天 ⋅ 0

maven顶级pom和子pom的版本号批量修改

当一个版本发布,新起一个版本时,我们只需要手动修改一下项目中pom.xml的版本号就可以了。但是如果这个maven项目有很多的子模块项目,那么一个个手动的去改就显得费时费力又繁琐了。还好,m...

ArlenXu ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部