《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄

news2024/11/28 15:51:23

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄

1.方法句柄出现的原因

某个国家举办了跑步比赛,有亚洲,欧洲还是非洲人参赛,但是有机器人也参赛了。机器人不属于人类阵营,怎么能让机器人也参加进来呢?


interface Human{
    void race();
}

class Asian implements Human{

   public void race(){
       System.out.println("asian running");
   } ;

}

class European implements Human{

    public void race(){
        System.out.println("european running");
    }

}

class African implements Human{
    public void race(){
        System.out.println("african running");
    }
}

class Robot{

    public void race(){
        System.out.println("robot running");
    }
}

通常我们的做法是创建一个包装类,将Robot的race方法也包装一下,如下:

class SpecialHuman implements Human{

    private Robot robot;


    @Override
    public void race() {
        robot.race();
    }
}

或者可以利用反射,去调用每个类中的race方法

方式一:调用方式

 //方式一:包装
        Asian asian = new Asian();
        European european = new European();
        African african = new African();

        //参赛选手集合
        ArrayList<Human> humans = new ArrayList<>();
        humans.add(african);
        humans.add(european);
        humans.add(asian);
        Robot robot = new Robot();
        SpecialHuman specialHuman = new SpecialHuman();
        specialHuman.setRobot(robot);
        humans.add(specialHuman);

        for (Human human : humans) {
            human.race();
        }

方式二:反射调用方法

 //方法二
        Asian asian = new Asian();
        European european = new European();
        African african = new African();
        Robot robot = new Robot();

        ArrayList<Object> objectArrayList = new ArrayList<>();

        objectArrayList.add(african);
        objectArrayList.add(european);
        objectArrayList.add(asian);
        objectArrayList.add(robot);

        for (Object o : objectArrayList) {
            Class<?> aClass = o.getClass();
            Method race = aClass.getMethod("race");
            race.invoke(o);
        }

比起直接调用,这两种方式都比较复杂,性能更加低效。为了解决这个问题,从Java7开始,引入了动态调用方法的invokedynamic。该指令的调用机制抽象出调用点这一个概念,并允许应用程序将调用点链接至任意符合条件的方法上。

作为 invokedynamic 的准备工作,Java 7 引入了更加底层、更加灵活的方法抽象 :方法句柄(MethodHandle)。

2.什么是方法句柄

方法句柄是一个强类型的,能够被直接执行的引用[2]。该引用可以指向常规的静态方法或者实例方法,也可以指向构造器或者字段。当指向字段时,方法句柄实则指向包含字段访问字节码的虚构方法,语义上等价于目标字段的 getter 或者 setter 方法,注意是语义上,并不是实际等于,可能实际上某个字段的get方法并不不是获取字段本身的值。

方法句柄 由 方法的形参列表和返回值类型进行匹配,与方法名和类名无关。

方法句柄的访问权限由LookUp的创建位置决定,与句柄的创建位置无关。在执行时不会被权限修饰符限制。如下的代码,LookUp创建在一个静态的public的方法中,因此Foo类相当于裸奔了,可以随时随地的调用bar方法,并且无需开启暴力反射

class Foo {
  private static void bar(Object o) {
    ..
  }
  public static Lookup lookup() {
    return MethodHandles.lookup();
  }
}

// 获取方法句柄的不同方式
MethodHandles.Lookup l = Foo.lookup(); // 具备Foo类的访问权限
Method m = Foo.class.getDeclaredMethod("bar", Object.class);
MethodHandle mh0 = l.unreflect(m);

MethodType t = MethodType.methodType(void.class, Object.class);
MethodHandle mh1 = l.findStatic(Foo.class, "bar", t);

image-20230514111432735

3.方法句柄的操作

句柄的调用

严格匹配参数类型的 invokeExact

方法句柄的调用可分为两种,一是需要严格匹配参数类型的 invokeExact。它有多严格呢?假设一个方法句柄将接收一个 Object 类型的参数,如果你直接传入 String 作为实际参数,那么方法句柄的调用会在运行时抛出方法类型不匹配的异常。正确的调用方式是将该 String 显式转化为 Object 类型。

比如还是上述的代码,我们把invoke改成invokeExact方法,执行就会报错,会告诉你参数期望类型不一致


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

class Foo {
        private static void bar(Object o) {
            System.out.println("bar 执行了");
        }
        public static MethodHandles.Lookup lookup() {
            return MethodHandles.lookup();
        }
    }

class Main2{

    public static void main(String[] args) throws Throwable {
        //获取方法对象
        Class<Foo> fooClass = Foo.class;
        Method bar = fooClass.getDeclaredMethod("bar", Object.class);

        //获取方法句柄
        MethodHandles.Lookup lookup = Foo.lookup();
        MethodHandle unreflect = lookup.unreflect(bar);
        unreflect.invokeExact(new String());
    }

}

image-20230514135756045

跟进方法会发现此本地方法的方法声明上有个@PolymorphicSignature注解。在碰到被它注解的方法调用时,Java 编译器会根据所传入参数的声明类型来生成方法描述符,而不是采用目标方法所声明的描述符。

image-20230514135844876

如下代码的字节码所示


import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

class Foo {
        private static void bar(Object o) {
            System.out.println("bar 执行了");
        }
        public static MethodHandles.Lookup lookup() {
            return MethodHandles.lookup();
        }
    }

class Main2{

    public static void main(String[] args) throws Throwable {
        //获取方法对象
        Class<Foo> fooClass = Foo.class;
        Method bar = fooClass.getDeclaredMethod("bar", Object.class);

        //获取方法句柄
        MethodHandles.Lookup lookup = Foo.lookup();
        MethodHandle unreflect = lookup.unreflect(bar);
        unreflect.invokeExact(new String());
        unreflect.invokeExact(new String());
    }

}

 0 ldc #2 <Foo>
 2 astore_1
 3 aload_1
 4 ldc #3 <bar>
 6 iconst_1
 7 anewarray #4 <java/lang/Class>
10 dup
11 iconst_0
12 ldc #5 <java/lang/Object>
14 aastore
15 invokevirtual #6 <java/lang/Class.getDeclaredMethod : (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;>
18 astore_2
19 invokestatic #7 <Foo.lookup : ()Ljava/lang/invoke/MethodHandles$Lookup;>
22 astore_3
23 aload_3
24 aload_2
25 invokevirtual #8 <java/lang/invoke/MethodHandles$Lookup.unreflect : (Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;>
28 astore 4
30 aload 4
32 new #9 <java/lang/String>
35 dup
36 invokespecial #10 <java/lang/String.<init> : ()V>
39 invokevirtual #11 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/String;)V>
42 aload 4
44 new #5 <java/lang/Object>
47 dup
48 invokespecial #1 <java/lang/Object.<init> : ()V>
51 invokevirtual #12 <java/lang/invoke/MethodHandle.invokeExact : (Ljava/lang/Object;)V>
54 return

image-20230514144257005

同时我们也能发现,方法句柄调用的指令是 invokevirtual,其实这个指令与原方法调用指令是一致的,此处是调用实列方法,因此为 invokevirtual

自动适配参数类型的 invoke

如果你需要自动适配参数类型,那么你可以选取方法句柄的第二种调用方式 invoke。它同样是一个签名多态性的方法。invoke 会调用 MethodHandle.asType 方法,生成一个适配器方法句柄,对传入的参数进行适配,再调用原方法句柄。调用原方法句柄的返回值同样也会先进行适配,然后再返回给调用者。

4.方法句柄调用目标方法

改写第一个比赛跑步代码

import jdk.nashorn.internal.runtime.Specialization;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Objects;

interface Human {
    void race();
}

class Asian implements Human {

    public void race() {
        System.out.println("asian running");
    };

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }

}

class European implements Human {

    public void race() {
        System.out.println("european running");
    }

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class African implements Human {
    public void race() {
        System.out.println("african running");
    }
    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class Robot {

    public void race() {
        System.out.println("robot running");
    }

    public static MethodHandles.Lookup lookup() {
        return MethodHandles.lookup();
    }
}

class SpecialHuman implements Human {

    private Robot robot;

    public Robot getRobot() {
        return robot;
    }

    public void setRobot(Robot robot) {
        this.robot = robot;
    }

    @Override
    public void race() {
        robot.race();
    }
}

public class RunningRace {

    public static void main(String[] args) throws Throwable {

            //拿到所有的Lookup,创建方法句柄
        MethodHandles.Lookup lookup1 = Asian.lookup();
        MethodHandles.Lookup lookup2 = European.lookup();
        MethodHandles.Lookup lookup3 = African.lookup();
        MethodHandles.Lookup lookup4 = Robot.lookup();

        Asian asian = new Asian();
        European european = new European();
        African african = new African();
        Robot robot = new Robot();

         lookup1.findVirtual(Asian.class, "race", MethodType.methodType(void.class)).invoke(asian);
        lookup2.findVirtual(European.class,"race", MethodType.methodType(void.class)).invoke(european);
        lookup3.findVirtual(African.class,"race", MethodType.methodType(void.class)).invoke(african);
        lookup4.findVirtual(Robot.class,"race", MethodType.methodType(void.class)).invoke(robot);

    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524321.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java递归生成树

1.建菜单表 CREATE TABLE t_menu ( id int(11) NOT NULL AUTO_INCREMENT, pid int(11) NOT NULL, name varchar(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT11 DEFAULT CHARSETutf8mb4; 2.造一些数据 注意&#xff1a;根节点的pid0&#xff0c…

利用Rsoft开展弯曲光纤仿真分析

Rsoft是一款优秀的光学仿真软件&#xff0c;里面集成了多个模块&#xff0c;其中BPM模块利用光束传播法&#xff08;Beam Propagation Method&#xff09;&#xff0c;能够进行多种类型光器件的仿真&#xff0c;比如分束器、光纤等。这次&#xff0c;利用该模块展示如何开展光纤…

SpringSecurity简单的练手项目(SpringBoot+SpringSecurity+JWT)

文章目录 一、项目介绍二、SpringSecurity简介SpringSecurity中的几个重要组件&#xff1a;1.SecurityContextHolder&#xff08;class&#xff09;2.SecurityContext&#xff08;Interface&#xff09;3.Authentication&#xff08;Interface&#xff09;4.AuthenticationMana…

Eclipse的介绍与安装

Eclipse简介 Eclipse 是一个开放源代码的&#xff0c;基于 Java 的可扩展开发平台。Eclipse官方版是一个集成开发环境(IDE)&#xff0c;可以通过安装不同的插件实现对其它计算机语言编辑开发&#xff0c;如C、Php、Python等等。 Eclipse的下载 下载时需要访问网址 http://…

Android系统原理性问题分析 - RefBase、sp、wp 分析

声明 在Android系统中经常会遇到一些系统原理性的问题&#xff0c;在此专栏中集中来讨论下。接触Android系统&#xff0c;遇到很多sp、wp相关问题&#xff0c;此篇分析Android系统内的智能指针问题。此篇参考一些博客和书籍&#xff0c;代码基于Android 9.0.0&#xff0c;不方…

3D点云的基本操作(基于PCL编程)

知识储备 右手系 右手&#xff0c;拇指&#xff0c;食指&#xff0c;中指&#xff0c;分别是x,y,z的正方向。左手系则同理。 旋转矩阵 本质&#xff1a;两个坐标系之间的旋转关系。 用途&#xff1a;旋转点云。 原理&#xff1a;设传感器的坐标系为O1X1Y1Z1&#xff0c;设…

mysql 分组语句测试

建表 建表语句&#xff1a; CREATE TABLE student( id int not null, name char(12), sex char(1) ); 预置数据 insert into student values(1, wh, 1); insert into student values(2, wh1, 0); insert into student values(3, zyx, 0); commit; 增加字段 alt…

设计模式的分类、意图和适用性

文章目录 引言分类创建型设计模式Factory Method&#xff08;工厂方法&#xff09;Abstract Factory&#xff08;抽象工厂&#xff09;Builder&#xff08;生成器&#xff09;Prototype&#xff08;原型&#xff09;Singleton&#xff08;单例&#xff09; 结构型设计模式Adapt…

【二】设计模式~~~创建型模式~~~工厂方法模式(Java)

【学习难度&#xff1a;★★☆☆☆&#xff0c;使用频率&#xff1a;★★★★★】 2.1. 模式动机 现在对该系统进行修改&#xff0c;不再设计一个按钮工厂类来统一负责所有产品的创建&#xff0c;而是将具体按钮的创建过程交给专门的工厂子类去完成&#xff0c;我们先定义一个…

【周末闲谈】超越ChatGPT?科大讯飞星火认知大模型

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 前言星火名字的由来科大讯飞星火落地应用演示赶超ChatGPT的底气在哪里?“硬…

洗地机哪个品牌好?好用的家用洗地机分享

洗地机采用高效吸力和清洗方式&#xff0c;可快速清除地面污渍和痕迹&#xff0c;让地面干净整洁&#xff0c;提高使用者的生活品质和舒适度。洗地机不仅清洁效果好&#xff0c;而且操作简单&#xff0c;大多采用一键启动和一键停止&#xff0c;方便快捷&#xff0c;节省时间和…

MySQL备份工具之xtrabackup

文章目录 MySQL备份工具之xtrabackup一、xtrabackup的介绍1、xtrabackup 版本兼容性2、Xtrabackup优点3、Xtrabackup备份原理 二、安装mysql5.7.x1、yum方式安装mysql5.7.x的方式2、下载 xtrabackup2.4 版本3、xtrabackup2.4备份mysql5.7.x数据3.1、innobackupex全备3.2、模拟数…

瑞吉外卖 - 完善后台系统登陆功能(5)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

北邮22信通:电子电路实验:分享一个存放零散电阻的小方法

北邮22信通一枚~ 很高兴以一个新身份和大家见面&#xff01; 有关电子电路实验的新专栏即将开启&#xff0c;会尽量分享一些实验报告方面的文章&#xff0c;大家敬请期待~ 这篇文章想和大家分享困扰小编好久的问题的解决方法&#xff01;同时也就作为专栏开启的引子啦~ 事…

DJI A3飞控 遥控器信号中断 会导致什么问题?

DJI A3飞控 遥控器信号中断 会导致什么问题&#xff1f; 在使用DJI A3 飞控的过程中&#xff0c;希望用OSDK完成自动化的任务。 DJI A3要求必须连接遥控器&#xff0c;可以是大疆Lightbridge的遥控器&#xff0c;也可以是SBUS协议的遥控器&#xff0c;比如航模的支持SBUS协议的…

【历史上的今天】4 月 17 日:Turbo Pascal 2.0 发布;PlayStation 遭受攻击;搜狐李善友辞职

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 17 日&#xff0c;在 1790 年的今天&#xff0c;电学奠基人富兰克林逝世。美国的杰出发明家本杰明富兰克林从 1746 年开始研究电的现象&#xff0c;通过反…

问卷调查设计攻略!这些原则步骤让你的结果更精准

调查问卷是从特定人群中收集数据的有效工具。在设计调查问卷时&#xff0c;我们必须仔细考虑研究目标、目标受众和所需信息的类型。调查问卷的设计原则和步骤对于确保所收集数据的准确性和可靠性非常重要。在本文中&#xff0c;我们将讨论问卷的设计原则和步骤。 一、问卷设计…

Vivado输入输出时序约束(set_input_delay、set_output_delay)

前言 I/O Delay约束主要有两个命令&#xff1a;set_input_delay和set_output_delay。 I/O Delay约束的主要目的同时钟约束一样&#xff0c;是告诉编译器&#xff0c;外部输入输出信号与参考时钟之间的相位关系&#xff0c;便于综合器能够真实和准确的对IO接口的信号进行…

打造一流软件测试工程师的技能图谱

目录 引言 测试工程师面临的核心问题 概述 测试设计 代码能力 阅读开发代码 自动化测试的开发 自动化测试 UI自动化 接口自动化 质量管理流程 行业技术知识 数据库 关系型数据库 非关系型数据库 RDBMS vs NoSQL 业务知识 技术的准备 一、测试基础 二、Linu…

IDEA配置Maven教程(超详细版~)

文章目录 前言一、Maven下载二、配置Maven环境变量三、settings.xml配置文件修改四、打开IDEA配置Maven 前言 本文介绍在IDEA中配置Maven 一、Maven下载 首先我们进入maven官方网站&#xff0c;进入网页后&#xff0c;点击Download去下载 下载免安装版&#xff0c;解压即可,…