注:这个例子原本出现在周志明先生的《深入理解Java虚拟机》--虚拟机字节码执行引擎章节,介于有读者朋友有疑问,这里基于Java代码层面解释一下(原文在《深入理解Java虚拟机》读书笔记(七)--虚拟机字节码执行引擎(下))。

这里直接把代码贴出来:

package test;import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;public class Test {static class GrandFather {void thinking() {System.out.println("i am grandfather");}}static class Father extends GrandFather {void thinking() {System.out.println("i am father");}}static class Son extends Father {void thinking() {try {MethodType methodType = MethodType.methodType(void.class);Field IMPL_LOOKUP = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");IMPL_LOOKUP.setAccessible(true);((MethodHandles.Lookup) IMPL_LOOKUP.get(null)).findSpecial(GrandFather.class, "thinking", methodType, Father.class).bindTo(this).invoke();} catch (Throwable e) {e.printStackTrace();}}}public static void main(String[] args) throws Throwable {Son son = new Son();son.thinking();}}

这里直接看Son类的thinking方法(关于为何这样实现,在《深入理解Java虚拟机》读书笔记(七)--虚拟机字节码执行引擎(下)中也解释了)。

关于这段代码,可以简单的理解findSpecial方法是为了找到方法,invoke是为了调用方法。由于找到的thinking方法是非static的,需要一个隐式入参(也就是栈帧中局部变量表第0个位置的this参数),在java中这叫做该方法的接收者。

在普通的方法调用中,这个this参数是虚拟机自动处理的,表示的是当前实例对象,我们在方法中可以直接使用。但是在我们这个MethodHandle的例子中,相当于是模拟了invoke*指令的处理,手动调用invoke方法就需要指定这个"this"参数。其实不只是"this"参数,其它参数也需要在invoke中传递。如果thinking方法的定义为:

void thinking(String str) {System.out.println("i am grandfather");}

那么invoke方法就需要两个参数,一个隐式的"this",一个String。所以应该这样调用:invoke(this,"string");如果这时调用invoke(this);或invoke("string")就会报错。

关于bindTo方法,其实就是指定方法的接收者,bindTo(this).invoke()和invoke(this)可以认为是一个意思。如下:

//
((MethodHandles.Lookup) IMPL_LOOKUP.get(null)).findSpecial(GrandFather.class, "thinking", methodType, Father.class).bindTo(this).invoke();
//和上面的调用是一个意思
((MethodHandles.Lookup) IMPL_LOOKUP.get(null)).findSpecial(GrandFather.class, "thinking", methodType, Father.class).invoke(this);

bindTo指定参数之后,在invoke方法中就不用指定this隐式参数了,不然会被当做普通参数去处理,就会出错。我觉得使用bindTo绑定方法接收者要比在invoke方法中传递更加友好,也更加符合程序员的大众理解,invoke可以只专注方法显式的入参。

然后再来说bindTo(this)中的this。前面提到了,这个this是我们当做方法接收者传过去的,那我们尝试在GrandFather的方法中把this打印出来看看:

static class GrandFather {void thinking() {System.out.println("i am grandfather\nthis.class:" + this.getClass());}}

结果输出:

i am grandfather
       this.class:class test.Test$Son

可以看到,这个class是$Son,并不是$GrandFather,因为我们没有使用GrandFather的实例对象,实际传入的是bindTo或invoke指定的实例对象。基于这个事实,我们这时可以直接在GrandFather的thinking方法中调用Son类独有的方法,使用反射或者直接类型强制转换为Son就行了。

同样的,如果将Son的thinking方法中的bindTo修改为Father对象,也就是bindTo(this)修改为bindTo(new Father()):

static class Son extends Father {void thinking() {try {MethodType methodType = MethodType.methodType(void.class);Field IMPL_LOOKUP = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");IMPL_LOOKUP.setAccessible(true);((MethodHandles.Lookup) IMPL_LOOKUP.get(null)).findSpecial(GrandFather.class, "thinking", methodType, Father.class).bindTo(new Father()).invoke();} catch (Throwable e) {e.printStackTrace();}}}

再看运行结果:

i am grandfather
       this.class:class test.Test$Father

但是bindTo方法的参数可不能随便传。这就要回到findSpecial方法中的第四个class类型的参数,即本例中使用的Father.class。这个参数中指定的是方法接收者的类型,bindTo指定的接收者的类型必须要是这个类或子类,不然会出现ClassCastException异常。因为在处理逻辑中需要做强转,然后再绑定方法接受者:

public MethodHandle bindTo(Object x) {Class<?> ptype;@SuppressWarnings("LocalVariableHidesMemberVariable")MethodType type = type();if (type.parameterCount() == 0 ||(ptype = type.parameterType(0)).isPrimitive())throw newIllegalArgumentException("no leading reference parameter", x);x = ptype.cast(x);  // 主要是这里,调用的是Class的cast方法。throw CCE if neededreturn bindReceiver(x);//绑定方法接收者}Class.cast方法如下:public T cast(Object obj) {if (obj != null && !isInstance(obj))throw new ClassCastException(cannotCastMsg(obj));return (T) obj;}

这个例子中要找祖父类的方法,findSpecial方法的第四个class类型必须是 Father.class或GrandFather.class了,这个就和invokespecial指令有关了(具体可以参照《深入理解Java虚拟机》读书笔记(七)--虚拟机字节码执行引擎(上))。我们也可以使用findVirtual找到该方法,不过就需要一个GrandFather的实例对象(当然也就不用使用反射了):

static class Son extends Father {void thinking() {try {MethodType methodType = MethodType.methodType(void.class);MethodHandles.lookup().findVirtual(GrandFather.class, "thinking", methodType).bindTo(new GrandFather()).invoke();} catch (Throwable e) {e.printStackTrace();}}}

不过这个其实就和new GrandFather().thinking()差不多了。

关于使用MethodHandle在子类中调用祖父类重写方法的探究相关推荐

  1. 在ABAP XSLT中调用ABAP类的方法

    本文介绍在ABAP XSLT中调用ABAP类的方法. 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

  2. c++与java中子类中调用父类成员的方法

    java中: import java.util.Scanner; public class ClassTest{ public static void main(String args[]){ chi ...

  3. 在子类中调用父类的方法super

    1.没有super之前,在子类里面需要父类里面的逻辑,但是我们是通过派生(自己定义了一个init,增加了一条line) class vehichle:#定义一个交通工具的类Country=" ...

  4. 继承实现的原理、子类中调用父类的方法、封装

    一.继承实现的原来 1.继承顺序 Python的类可以继承多个类.继承多个类的时候,其属性的寻找的方法有两种,分别是深度优先和广度优先. 如下的结构,新式类和经典类的属性查找顺序都一致.顺序为D--- ...

  5. Python 在子类中调用父类方法详解(单继承、多层继承、多重继承)

    Python 在子类中调用父类方法详解(单继承.多层继承.多重继承)   by:授客 QQ:1033553122   测试环境: win7 64位 Python版本:Python 3.3.5 代码实践 ...

  6. java 跨类 调用 model_Model.java中的这两个方法,为什么不能在子类中调用,或者包内调用也行啊。...

    @JFinal 你好,想跟你请教个问题: Model.java中的这两个方法,为什么不能在子类中调用,或者包内调用也行啊. /** * Find model. */ @SuppressWarnings ...

  7. Day19:继承实现的原理、子类中调用父类的方法、封装

    一.继承实现的原来 1.继承顺序 Python的类可以继承多个类.继承多个类的时候,其属性的寻找的方法有两种,分别是深度优先和广度优先. 如下的结构,新式类和经典类的属性查找顺序都一致.顺序为D--- ...

  8. qt 子类调用父类的函数_子类中调用父类的方法

    父类名 . __init__(self, ...) 可以将父类中的init中的属性重复调用,减少代码的重复 class Vehicle: def __init__(self, name, speed, ...

  9. Python在子类中调用父类方法

    1.在子类中调用父类方法 super().方法名() 类名.方法名(self) spuer(要从哪一个类的上一级类开始查找, self).方法名() 子类调用父类方法时,一般都是想对父类方法进行扩展 ...

最新文章

  1. jsp 分页 tag
  2. [转] MMO即时战斗:地图角色同步管理和防作弊实现
  3. php 正则教程,最通俗易懂的php正则表达式教程(上)
  4. 农林行业的系统应用 php专业,农林|类专业
  5. mac bash file密码_Mac系统 | 菜鸟程序员项目模拟数据迁移,会安装Mysql服务端吗
  6. Keil forc51安装教程
  7. 信息学奥赛一本通(1154:亲和数)
  8. 有关UIView、subview的几个基础知识点-IOS开发 (实例)
  9. Windows软件路由器运用实例之OSPF配置
  10. 安装TensorFlow-gpu的注意点
  11. paip.提升用户体验---c++ qt自定义窗体(1)---标题栏的绘制
  12. WSO2 IS 添加新的证书域名
  13. 手机连接wifi时使用固定mac地址
  14. a16z 合伙人:Web3 的新思维、新策略和新指标
  15. “BAT”前员工创业:风投很待见
  16. 自学C语言的最大难题是什么?
  17. 冈萨雷斯《数字图像处理matlab版》(一):绪言
  18. 「5.29 开源市集参与指南」因为相信所以看见
  19. 巴比特 | 元宇宙每日必读:中国银行公布元宇宙支付相关专利,阿里申请多个淘宝元宇宙商标,元宇宙商标、专利竞争加速?...
  20. Java并发编程(中下篇)从入门到深入 超详细笔记

热门文章

  1. 英特尔第11代处理器(Intel Tiger Lake) 安装Windows 10时找不到驱动器
  2. java跳蚤市场源码,基于JAVA的校园网上跳蚤市场的设计与实现.doc
  3. 人工智能方向开发环境说明
  4. 计算机网络功能主要体现在哪两个方面,计算机网络的功能主要体现在哪三个方面...
  5. 从零开始的技术美术之路(六)色彩空间
  6. 前端常见配置文件格式及选择
  7. ICP备案与ICP许可证有什么区别?怎么办理ICP许可证?
  8. SQL语句----DML
  9. idesk卸载教程_【亲测可行】Autodesk 卸载工具,一键完全彻底卸载删除autodesk软件专门卸载工具...
  10. html5 scrollheight,JavaScript之scrollTop、scrollHeight、offsetTop、offsetHeight等属性学习笔记...