何为MethodHandles?

news2024/9/20 14:39:46

最近在梳理ThreadPoolExecutor,无意间看到其内部类Worker实现了一个名字叫做AbstractQueuedSynchronizer的抽象类。看到它,我便想起当年为了面试而疯狂学习这个知识点的场景。不过这种临时抱佛脚的行为,并未给我带来即时的收益。也是这次的疯狂,我的舒适区又向外扩展了一点。通过这个类我接触到了一个名为Node的最终静态内部类。这里不得不啰嗦两句:AQS通过内置的FIFO来完成获取资源线程的排队工作的(说白了就是对线程进行排队)。这个内置的同步队列被成为CLH队列,这个队列是由一个个的Node节点组成的,这个Node节点就是AbstractueuedSynchronizer中的静态内部类Node。注意:Node节点维护了一个prev引用和一个next引用,分别指向自己的前驱节点和后继节点,而AQS维护了两个指针,分别指向队列头部head和尾部tail。不过这些并非重点,本节的重点是Node类中用到的MethodHandles类。【注意:本篇文章整理自《Java MethodHandles介绍与反射对比区别详解》,其链接地址为:https://www.jb51.net/program/306162bll.htm】

1 概述

MethodHandles这个类是java 7引入的一个新的API,其位于java.lang.invoke包中。网络一篇博文对其的解释为:它是在Java 7中引入的,并在以后的jdk版本中得到了增强。在学习这个API之前我们首先要明白什么是方法句柄(method handles)

所谓方法句柄是指对基础方法、构造函数、字段或类似低级操作的类型化、直接可执行的引用,具有参数或返回值的可选转换。更简单地讲,方法句柄是一种用于查找、调整和调用方法地低级机制。方法句柄是不可变地,并且没有可见的状态。要创建和使用MethodHandle,需要4个步骤

  1. 创建lookup
  2. 创建method type
  3. 查找方法句柄
  4. 调用方法句柄

读完这些,我还是不知道方法句柄到底是什么,只能小和尚念经一般,死记硬背。我只知道在工作中遇到操作类中某个属性,而又不想无脑new对象的情况时,会通过java提供的反射机制去创建对象、操作属性。这里的反射机制和方法句柄之间又有什么关系呢?参考文章中是这样描述的:引入方法句柄是为了与现有的java.lang.reflect API一起工作,因为它们具有不同的用途和不同的特性从性能角度来看,MethodHandles API可能比Reflection API快得多,因为访问检查是在创建时而不是在执行时进行的。如果存在安全管理器,则这种差异会被放大,因为成员和类查找要接受额外的检查。然而,考虑到性能并不是任务的唯一适用性度量,我们还必须考虑到,由于缺乏成员类枚举、可访问性标志检查等机制,此时MethodHandles API更难使用。即便如此,MethodHandles API提供了柯里化方法、更改参数类型和更改其顺序的可能性。

通过这段描述,我大概提炼出了这样一些结论:MethodHandles可以与现有的反射机制一起工作。相比之下前者在性能上具有优势,但是从其他方面考虑(譬如成员类枚举、可访问性标志检查等),后者又比前者有优势。个人觉得与其花费精力琢磨概念定义,不如从实践中探索概念和定义的真正用意:

  • 创建Lookup对象(博客原文有这样一段描述:“当我们想要创建方法句柄时,要做的第一件事是检索查找Lookup,即负责为查找类可见的方法、构造函数和字段创建方法句柄的工厂对象。通过MethodHandles API,可以创建具有不同访问模式的查找对象。”说实话,作者的段位实在不是本人这种小白所能企及的,所以我还是按照个人的理解描述一下,一来是希望通过描述让自己真正明白作者的意思,二来也希望读者可以提出一些批评。个人理解作者想要表达:创建方法句柄,要做的第一件事情就是创建Lookup检索对象。这个Lookup是一个工厂对象,用于为待查找类上的可见方法、构造函数、字段等创建方法句柄。)我们可以通过MethodHandles类提供的不同API来创建具有不同访问模式的查找对象,比如:通过publicLookup()方法可以创建一个只提供对公共方法访问的查找对象;通过lookup()方法可以创建一个提供对公共方法和私有方法访问的查找对象。(不好意思,我又抄了原文。说白了,通过publicLoockup()方法创建的Lookup对象只能访问待查找类中的公共方法;通过lookup()方法创建的Lookup对象既可以访问待查找类中的公共方法,也能访问待查找类中的私有方法
  • 创建MethodType对象(博客原文这样描述:为了能够创建MethodHandle,查找对象需要其类型的定义,这是通过MethodType类实现的。特别是,MethodType表示方法句柄接受和返回的参数和返回类型,或方法句柄调用程序传递和期望的参数和返回类型。MethodType的结构很简单,它由一个返回类型和适当数量的参数类型组成,这些参数类型必须在方法句柄及其所有调用方之间正确匹配。与MethodHandle相同,即使是MethodType的实例也是不可变的。让我们看看如何定义一个MethodType,该MethodType将java.util.List类指定为返回类型,将Object数组指定为输入类型:MethodType mt = MethodType.methodType(List.class, Object[].class)。如果方法的返回基本类型或void作为其返回类型,我们将使用表示这些类型的类(void.class, int.class)。让我们定义一个返回int类型值并接受Object的MethodType:MethodType mt = MethodType.methodType(int.class, Object.class)。很庆幸我读懂了这段话:创建MethodType的目的是为了更好地创建MethodHandle对象。MethodType是一个不可变类,是一个包装方法返回值及参数的对象,创建MethodType对象的方式是调用MethodType上的静态方法methodType()方法,该方法的第一个参数表示的是方法返回值,后一个参数表示的方法的参数集)
  • 找到方法句柄(博客原文这样描述:一旦我们定义了方法类型,为了创建MethodHandle,我们必须通过lookup或publicLookup对象找到它,同时提供原始类和方法名称。特别是,查找工厂提供了一组方法,使我们能够在考虑方法范围的情况下以适当的方式找到方法句柄。从最简单的场景开始,让我们探究主要的应用场景。a.方法的MethodHandle:使用findVirtual()方法可以为对象方法创建一个MethodHandle。下面就根据String类的concat()方法创建一个——MethodType mt=MethodType.methodType(String.class, String.class);MethodHandle concatMH = publiclook.findVirtual(String.class, “concat”, mt)。b. 静态方法的方法句柄。当要访问静态方法时,可以使用findStatic()方法——MethodType mt=MethodType.methodType(List.class, Object[].class);MethodHandle concatMH = publiclook.findStatic(Arrays.class, “asList”, mt),本例中创建了一个方法句柄,用于将对象数组转换为对象列表。c. 构造函数的方法句柄,可以使用findConstructor()方法访问构造函数。让我们创建一个方法句柄,它充当Integer类的构造函数,接受String属性——MethodType mt=MethodType.methodType(void.class, String.class);MethodHandle concatMH = publiclook. findConstructor(Integer.class, mt)。d. 字段的方法句柄。使用方法句柄也可以访问字段。先决条件是方法句柄和声明的属性之间具有直接访问可见性,下面创建一个充当getter的方法句柄:MethodHandle getTitleMH = lookup.findGetter(Book.class, “title”, String.class);。e.私有方法的方法句柄。在java.lang.reflect的API的帮助下,可以为私有方法创建方法句柄。具体过程为:Method formatBookMethod=Book.class.getDeclaredMethod(“formatBook”); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod))
  • 使用方法句柄。(博客原文是这样描述的:一旦我们创建了方法句柄,下一步就是使用它们。MethodHandle类提供了3种不同的方法来执行方法句柄:invoke()、invokeWithAruments()和invokeExact()。a. 当使用invoke()方法时,我们强制要固定的参数数量,但我们允许执行参数和返回类型的强制转换和装箱/取拆箱,String output=(String)replaceMH.invoke(“jovo”, Character.valueOf(‘o’), ‘a’),在这种情况下,replaceMH需要char参数,但invoke()在执行之前会对Character参数执行开箱操作。b. 使用参数调用。使用invokeWithArguments方法调用方法句柄是三个选项中限制最小的一个。事实上,除了参数和返回类型的强制转换和装箱/取消装箱外,它还允许变量arity调用。在实践中,这允许我们从一个int值数组开始创建一个Integer列表:List<Integer> list = (List<Integer>)asList.invokeWithArguments(1,2)。c. 调用Exact。如果我们想在执行方法句柄的方式上更加严格(参数的数量及其类型),我们必须使用invokeExact()方法。事实上,它没有为所提供的类提供任何类型转换,并且需要固定数量的参数。让我们看看如何使用方法句柄对两个int值求和:int sum = sumMH.invokeExact(1, 11)。如果在这种情况下,我们决定向invokeExact方法传递一个不是int的数字,那么调用将导致WrongMethodTypeException)

这篇博客还提到了一个使用数组的情况,关于这种情况下的具体信息,这里不再罗列参考原文即可,这里只列一下原文中的描述镜像,具体见下面这幅图片:

文中针对Java 9对于MethodHandles的增强也做了一些介绍,这里会做一下摘录,以便于学习:在Java9中,对MethodHandles API进行了一些增强,目的是使其更易于使用。这些增强主要有3个方面的影响:

  • 查找函数–允许从不同上下文中查找类,并支持接口中的非抽象方法
  • 参数处理——改进参数折叠、参数收集和参数传播功能
  • 附加组合–添加循环(loop、whileLoop、doWhileLoop…),并通过tryFinally提供更好的异常处理支持

这些变化带来了一些额外的好处:

  • 增加JVM编译器优化
  • 实例化减少
  • 在使用MethodHandles API时启用了精度

有了MethodHandles API的清晰定义和目标,我们现在可以从lookup开始使用它们。

2 总结

通过这篇文章,我对MethodHandles API有了一定的了解,明白了它们的基本用法,同时也了解了它与反射API之间的关系。其实在我的认知中MethodHandles是一个操作类中相关元素的一种方式。根据前面的梳理,这个API可以操作的元素有:方法、构造方法、静态方法、字段等等。相较于反射API这种方式相对简单,同时还会带来其他好处,就像前面个讲的那样:MethodHandles API可能比Reflection API快得多。回过头来看上一节梳理AQS时遇到的Node类,其中有这样一段代码: 

private static final VarHandle NEXT;
private static final VarHandle PREV;
private static final VarHandle THREAD;
private static final VarHandle WAITSTATUS;
static {
    try {
        MethodHandles.Lookup l = MethodHandles.lookup();
        NEXT = l.findVarHandle(Node.class, "next", Node.class);
        PREV = l.findVarHandle(Node.class, "prev", Node.class);
        THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
        WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
    } catch (ReflectiveOperationException e) {
        throw new ExceptionInInitializerError(e);
    }
}

这里用findVarHandle()方法创建一个VarHandle对象,用于操作Node上的next、prev、thread及waitStatus属性。VarHandle是Java9引入的一个工具,其提供了更细粒度的内存屏障,保证共享变量读写可见性、有序性、原子性。提供了更好的安全性和可移植性,替代Unsafe的部分功能。其常见方法如下图所示【注意关于VarHandle的描述,可参见参见《VarHandle:Java9中保证变量读写可见性、有序性、原子性利器》这篇博客,其链接地址为:https://blog.51cto.com/u_13540373/6898592】:

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

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

相关文章

基于Java的高校学生工作系统的设计与实现(论文+源码)_kaic

基于Java的高校学生工作系统的设计与实现(论文源码)_kaic 摘 要 本系统为高校学生工作管理系统&#xff0c;系统能够为高校提供便捷的学生信息管理功能。该系统采用 Java 语言编写&#xff0c;系统采用MVC架构进行设计&#xff0c;通过Servlet和JSP等技术实现前后端数据交互和…

【漏洞复现】SuiteCRM responseEntryPoint Sql注入漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

基于UDS的Flash 刷写——BootLoad刷写流程详解

从0开始学习CANoe使用 从0开始学习车载测试 相信时间的力量 星光不负赶路者&#xff0c;时光不负有心人。 目录 流程概述UDS流程详解释前编程①诊断会话控制 - 切换到扩展会话&#xff08;10 03&#xff09;②例程控制-预编程条件检查&#xff08;31 01 02 03&#xff09;③DTC…

ClickHouse分布式部署搭建单分片二副本集群

搭建单分片二副本集群,使用MergeTree引擎测试数据同步 服务器: 127.0.0.1 clickhouse 127.0.0.2 clickhouse + keeper 结构图 1.修改hosts vi /etc/hosts 添加需要部署的ip和名字 127.0.0.1 node1 127.0.0.2 node2 2. node1配置文件修改 2.1 修改/etc/clickhouse-se…

Excel中使用VBS自定义函数将中文转为拼音首字母

1、在“开发工具”中&#xff0c;点击“Visual Basic”。如果没有“开发工具”&#xff0c;则添加。 2、添加“模块”&#xff0c;在窗口中添加自定义函数。 Function MyGetPYChar(char) MyCodeNumber 65536 Asc(char) If (MyCodeNumber > 45217 And MyCodeNumber <…

【网络安全】缓存配置错误导致授权绕过

未经许可,不得转载。 文章目录 正文复现正文 一个电子商务网站,它有 2 个资产:target.com和admin.target.com target.com是面向用户的门户,用户可以去那里购买物品。admin.target.com是卖家的管理门户,卖家可以在其中列出他们的物品,跟踪订单、客户信息等。 我正在测试…

有希带你深入理解指针(3)

前言 本篇文章是对指针知识的进一步讲解&#xff0c;如果对部分知识有不了解的地方可以移步前文进行学习&#xff01; 1.字符指针变量 该内容我们在前面的文章中已提到过&#xff0c;想必大家对它应该不陌生吧&#xff01;这里我们会对它进行详细的介绍。 一般情况下&#xf…

FPGA开发——IIC实现简单的串口回环

一、概述 在我们进行日常开发时&#xff0c;不管是进行MCU、单片机、还是FPGA&#xff0c;都会使用到IIC通信协议。采用串行总线可以简化系统硬件结构、减小系统体积、提高系统可靠性。常 用的串行总线有单总线&#xff08;1-Wire Bus&#xff09;、IIC&#xff08;Inter-Integ…

Codeforces Round 926 (Div. 2) C. Sasha and the Casino (博弈论*1400)

这里的意思是想让我们求得是否是能够实现不停地无上限的赚钱。 这里注意避开一个思维误区&#xff0c;如果你想的是前x次一直用1枚硬币然后吃第x1次保底&#xff0c;那么就是错误的。你应该考虑到如果前x次里面出现了胜利呢&#xff1f;这时候你拿着一枚硬币根本赚不回本。 所…

全志H616系统启动和登录

一、系统启动 刷完机烧入镜像&#xff0c;直接用MobaXterm软件串口登陆 约定固定的波特率115200。 默认登录&#xff1a; 用户&#xff1a;orangepi 密码&#xff1a;orangepi 或用户&#xff1a;root 密码&#xff1a;orangepi 在输入密码时…

YOLO 单目测距:原理、方法与代码

一、原理 单目测距的一个常见方法是假设物体的尺寸已知。通过测量物体在图像中的高度&#xff08;或宽度&#xff09;&#xff0c;并结合物体的实际高度&#xff08;或宽度&#xff09;&#xff0c;最简单的一种方式就是利用相似三角形的原理来计算物体的距离。 二、相似三角…

使用深度学习来进行击剑动作识别的裁判工作

在击剑比赛中&#xff0c;当双方几乎同时击中对方时&#xff0c;记分板两边都会亮起。这时裁判需要决定哪一方得分。一般而言&#xff0c;谁更主动或控制了局势就会得分。我尝试训练了一个模型来辅助裁判做这样的判断&#xff01;目前该模型在花剑测试集上的准确率大约为60%&am…

Vue开发者工具安装详细教程

欢迎大家订阅【Vue2Vue3】入门到实践 专栏&#xff0c;开启你的 Vue 学习之旅&#xff01; 文章目录 前言一、下载二、安装三、调试 前言 Vue 是一个框架&#xff0c;也是一个生态&#xff0c;其功能覆盖了大部分前端开发常见的需求。本文详细讲解了 Vue 开发者工具的安装。 …

ES7.17.5 float类型 terms带来的隐患

背景 1.用户在mapping中加一个字段 testid&#xff0c;结果写数据的时候使用 testId&#xff0c;同时也没有strict限制动态mapping&#xff0c;只是使用了默认的 true&#xff0c;即允许动态生成mapping 2.动态生成的字段 testId 被识别成了 float&#xff0c;用户为了方便&a…

【Netty 一】

Netty是什么 Netty 是一个高性能、异步事件驱动的 NIO 框架&#xff0c;基于 JAVA NIO 提供的 API 实现。它提供了对 TCP、 UDP 和文件传输的支持&#xff0c;作为一个异步 NIO 框架&#xff0c; Netty 的所有 IO 操作都是异步非阻塞 的&#xff0c; 通过 Future-Listener 机制…

ssrf漏洞之——漏洞复现

漏洞介绍 SSRF漏洞&#xff1a;SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由恶意访问者构造url&#xff0c;由服务端对此url发起请求的一个安全漏洞。 漏洞原理 SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&#xff0c;并且没有对目…

Autosar(Davinci) --- 创建一个Implementation Data Types

前言 这里我们讲一下如何创建一个Implementation Data Types&#xff08;IDT) 一、什么是IDT 二、如何创建一个IDT 鼠标右键【Implementation Data Types】,选择【new Type Reference...】 起一个名字【IdtDoorState】&#xff0c;Data Types选择【boolean】&#xff0c;这里…

RFID光触发标签应用于制造业供应链管理的应用与探索

制造业作为国民经济的支柱产业&#xff0c;其供应链管理的复杂性和重要性日益凸显&#xff0c;在全球化竞争的背景下&#xff0c;企业需要更高效、更精准、更智能的供应链解决方案来满足市场需求&#xff0c;提高客户满意度&#xff0c;降低运营成本&#xff0c;RFID光触发标签…

【mysql】mysql的卸载和安装

mysql的卸载 mysql是否安装&#xff1a; 首先我们先来看看mysql是否安装&#xff1a; 快捷键winR输入cmd&#xff0c;进入命令输入框 输入mysql --version 查看mysql的版本 如果出现了mysql的版本就说明你已经安装了 系统用户root -p就是输入密码所以代码如下 mysql -ur…

AI大模型编写多线程并发框架(六十一):从零开始搭建框架

系列文章目录 文章目录 系列文章目录前言一、项目背景二、第一轮对话-让AI大模型理解我们的诉求二、第二轮对话-优化任务处理方法和结果处理方法三、参考文章 前言 在这个充满技术创新的时代&#xff0c;AI大模型正成为开发者们的新宠。它们可以帮助我们完成从简单的问答到复杂…