MethodHandle 类

定义

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

[Android.Runtime.Register("java/lang/invoke/MethodHandle", ApiSince=26, DoNotGenerateAcw=true)]
public abstract class MethodHandle : Java.Lang.Object
[<Android.Runtime.Register("java/lang/invoke/MethodHandle", ApiSince=26, DoNotGenerateAcw=true)>]
type MethodHandle = class
    inherit Object
继承
MethodHandle
属性

注解

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。 这些转换相当普遍,包括 #asType 转换、#bindTo 插入、java.lang.invoke.MethodHandles#dropArguments 删除和 java.lang.invoke.MethodHandles#filterArguments 替换等模式。

<h1>方法句柄内容</h1> 方法句柄根据其参数和返回类型动态且强类型化。 它们不按其基础方法的名称或定义类进行区分。 必须使用符号类型描述符调用方法句柄,该描述符与方法句柄自己的 #type 类型描述符匹配。

每个方法都通过 #type type 访问器报告其类型描述符。 此类型描述符是一个 java.lang.invoke.MethodType MethodType 对象,其结构是一系列类,其中一个是方法的返回类型(或者 void.class 如果没有)。

方法句柄的类型控制它接受的调用的类型以及应用于它的转换类型。

方法句柄包含一对调用和#invoke invoke调用#invokeExact invokeExact的特殊调用程序方法。 这两个调用方方法都提供对方法句柄的基础方法、构造函数、字段或其他操作的直接访问权限,由参数转换和返回值修改。 这两个调用程序都接受与方法句柄自己的类型完全匹配的调用。 普通的、不精确的调用程序也接受一系列其他调用类型。

方法句柄不可变,没有可见状态。 当然,它们可以绑定到显示状态的基础方法或数据。 对于Java内存模型,任何方法句柄的行为都如同其所有(内部)字段都是最终变量一样。 这意味着,对应用程序可见的任何方法句柄将始终完全形成。 即使方法句柄通过数据争用中的共享变量发布,也是如此。

方法句柄不能由用户子类化。 实现可以(或可能不)创建内部子类,这些子类 MethodHandle 可以通过操作可见 java.lang.Object#getClass Object.getClass 。 程序员不应从其特定类中得出有关方法句柄的结论,因为方法句柄类层次结构(如果有)可能会不时或从不同的供应商的实现中更改。

<h1>方法句柄编译</h1> Java方法调用表达式命名invokeExact,或者invoke可以从Java源代码调用方法句柄。 从源代码的角度来看,这些方法可以采用任何参数,其结果可以强制转换为任何返回类型。 通过为调用方方法 Object 提供返回类型和可变 arity Object 参数来实现此目的,但它们具有一种称为 <em>签名多态<性/em> 的附加质量,它将这种调用自由直接连接到 JVM 执行堆栈。

与虚拟方法一样,对指令进行源级调用 invokeExactinvoke 编译 invokevirtual 。 更不寻常的是,编译器必须记录实际参数类型,并且可能不会对参数执行方法调用转换。 相反,它必须根据自己的非转换类型将它们推送到堆栈上。 方法句柄对象本身在参数之前推送到堆栈上。 然后,编译器使用符号类型描述符调用方法句柄,该描述符描述参数和返回类型。

若要发出完整的符号类型描述符,编译器还必须确定返回类型。 这基于方法调用表达式的强制转换,如果有,或者 Object 调用是表达式,或者 void 调用是语句。 强制转换可能为基元类型(但不能 void)。

作为一个角落情况,未转换 null 的参数被赋予符号类型描述符 java.lang.Void。 该类型的 Void 不明确性是无害的,因为除了 null 引用之外没有类型的 Void 引用。

<h1>方法句柄调用</h1> 第一次 invokevirtual 执行指令时,通过以符号方式解析指令中的名称并验证方法调用是否静态合法来链接它。 这是调用 invokeExactinvoke。 在这种情况下,会检查编译器发出的符号类型描述符是否具有正确的语法,并解析它包含的名称。 因此, invokevirtual 只要符号类型描述符格式正确且类型存在,调用方法句柄的指令将始终链接。

invokevirtual链接后执行该操作时,JVM 首先检查接收方法句柄的类型,以确保它与符号类型描述符匹配。 如果类型匹配失败,则表示调用方调用的方法在调用的单个方法句柄上不存在。

在这种情况下 invokeExact,调用的类型描述符(解析符号类型名称后)必须与接收方法句柄的方法类型完全匹配。 在纯类型描述符的情况下,解析 invoke的类型描述符必须是接收方 #asType asType 方法的有效参数。 因此,纯素 invokeinvokeExact

类型匹配后,直接 invokeExact 和立即调用方法句柄的基础方法(或其他行为,如情况所示)。

如果调用方指定的符号类型描述符与方法句柄自己的类型完全匹配,则对普通 invoke 调用的调用的工作方式与调用 invokeExact的工作方式相同。 如果类型不匹配, invoke 则尝试调整接收方法句柄的类型,就像通过调用 #asType asType来获取完全可调用的方法句柄 M2一样。 这允许调用方和被调用方之间更强大的方法类型协商。

(<em>注意:</em> 调整后的方法句柄 M2 无法直接观测,因此不需要实现实现。

<h1>调用检查</h1> 在典型的程序中,方法句柄类型匹配通常会成功。 但是,如果匹配失败,JVM 将直接(在这种情况下invokeExact)或间接引发一个WrongMethodTypeException,就像通过失败的调用(asType在这种情况下invoke)。

因此,方法类型不匹配(可能在静态类型化程序中显示为链接错误)可以在使用方法句柄的程序中显示为动态 WrongMethodTypeException

由于方法类型包含“live” Class 对象,因此方法类型匹配将同时考虑类型名称和类加载程序。 因此,即使方法句柄是在一个类加载器L1中创建的,并在另一L2个类中使用,方法句柄M调用是类型安全的,因为调用方的符号类型描述符(如在解析中L2)与原始被调用方方法的符号类型描述符匹配(如中解析)。L1 创建并分配其类型时M,会发生解决方法L1,而在链接指令时invokevirtual发生L2解析。

除了检查类型描述符外,方法句柄调用其基础方法的功能不受限制。 如果方法句柄由有权访问该方法的类在非公共方法上形成,则任何接收对该方法的引用的调用方都可以在任何位置使用生成的句柄。

与核心反射 API 不同,每次调用反射方法时都会检查访问权限,在创建方法句柄时执行方法句柄访问检查。 对于 ldc (请参阅下文),访问检查是链接常量池条目的基础常量方法句柄的一部分。

因此,非公共方法的句柄或非公共类中的方法通常应保密。 不应将其传递给不受信任的代码,除非从不受信任的代码使用是无害的。

<h1>方法句柄创建</h1> Java代码可以创建直接访问该代码可访问的任何方法、构造函数或字段的方法句柄。 这是通过一 java.lang.invoke.MethodHandles.Lookup MethodHandles.Lookup 个反射的基于功能的 API 完成的,例如,可以从中 java.lang.invoke.MethodHandles.Lookup#findStatic Lookup.findStatic获取静态方法句柄。 核心反射 API 对象也有转换方法,例如 java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect

与类和字符串一样,对应于可访问字段、方法和构造函数的方法句柄也可以直接在类文件的常量池中表示为字节 ldc 码加载的常量。 新类型的常量池条目CONSTANT_MethodHandle,直接引用关联的CONSTANT_MethodrefCONSTANT_InterfaceMethodrefCONSTANT_Fieldref常量池条目。 (有关方法句柄常量的完整详细信息,请参阅 Java 虚拟机规范的 4.4.8 和 5.4.3.5 部分。

方法句柄由查找或常量加载从具有变量 arity 修饰符位(0x0080)的方法或构造函数生成的函数具有相应的变量 arity,就好像它们是在帮助 #asVarargsCollector asVarargsCollector下定义的一样。

方法引用可以引用静态或非静态方法。 在非静态情况下,方法句柄类型包括显式接收方参数,在任何其他参数之前追加。 在方法句柄的类型中,根据最初请求该方法的类键入初始接收方参数。 (例如,如果通过 ldc获取非静态方法句柄,接收方的类型是常量池条目中命名的类。

方法句柄常量受到相同的链接时间访问检查其相应的字节码指令,如果字节码行为会引发此类错误,指令 ldc 将引发相应的链接错误。

为此,对受保护成员的访问仅限于访问类或其子类之一的接收方,访问类必须是受保护成员定义类的子类(或包同级)。 如果方法引用引用当前包外部的类的受保护非静态方法或字段,接收方参数将缩小到访问类的类型。

调用虚拟方法的方法句柄时,始终在接收方(即第一个参数)中查找该方法。

也可以创建对特定虚拟方法实现的非虚拟方法句柄。 这些操作不基于接收方类型执行虚拟查找。 此类方法句柄模拟指令对同一 invokespecial 方法的影响。

<h1>用法示例</h1> 下面是一些用法示例: <blockquote>

{@code
            Object x, y; String s; int i;
            MethodType mt; MethodHandle mh;
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            // mt is (char,char)String
            mt = MethodType.methodType(String.class, char.class, char.class);
            mh = lookup.findVirtual(String.class, "replace", mt);
            s = (String) mh.invokeExact("daddy",'d','n');
            // invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
            assertEquals(s, "nanny");
            // weakly typed invocation (using MHs.invoke)
            s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
            assertEquals(s, "savvy");
            // mt is (Object[])List
            mt = MethodType.methodType(java.util.List.class, Object[].class);
            mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
            assert(mh.isVarargsCollector());
            x = mh.invoke("one", "two");
            // invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
            assertEquals(x, java.util.Arrays.asList("one","two"));
            // mt is (Object,Object,Object)Object
            mt = MethodType.genericMethodType(3);
            mh = mh.asType(mt);
            x = mh.invokeExact((Object)1, (Object)2, (Object)3);
            // invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
            assertEquals(x, java.util.Arrays.asList(1,2,3));
            // mt is ()int
            mt = MethodType.methodType(int.class);
            mh = lookup.findVirtual(java.util.List.class, "size", mt);
            i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
            // invokeExact(Ljava/util/List;)I
            assert(i == 3);
            mt = MethodType.methodType(void.class, String.class);
            mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
            mh.invokeExact(System.out, "Hello, world.");
            // invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
            }

</blockquote> 上述每个调用 invokeExact 或纯 invoke 调用都会生成一个 invokevirtual 指令,其中包含以下注释中指示的符号类型描述符。 在这些示例中,假定帮助程序方法 assertEquals 是对其参数调用 java.util.Objects#equals(Object,Object) Objects.equals 的方法,并断言结果为 true。

<h1>Exceptions</h1> 方法 invokeExactinvoke 声明为引发,也就是说,没有对方法句柄可以引发 java.lang.Throwable Throwable的静态限制。 由于 JVM 不区分已检查的异常和未选中的异常(当然由其类除外),因此对字节码形状没有特殊影响,从将检查的异常归为方法处理调用。 但在Java源代码中,执行方法句柄调用的方法必须显式引发,否则必须在本地捕获所有可引发Throwable的,只引发上下文中合法的调用,并包装非法的调用。

<h1>“sigpoly”>签名多态性</h1> 异常编译和链接行为 invokeExactinvoke 术语 <em>签名多态<性/em> 引用。 如Java语言规范中所述,签名多态方法是一种可以与各种调用签名和返回类型一起操作的方法。

在源代码中,无论请求的符号类型描述符如何,对签名多态方法的调用都将编译。 与往常一样,Java编译器针对命名方法发出invokevirtual具有给定符号类型描述符的指令。 异常部分是符号类型描述符派生自实际参数和返回类型,而不是从方法声明派生。

当 JVM 处理包含签名多态调用的字节码时,它将成功链接任何此类调用,而不考虑其符号类型描述符。 (为了保持类型安全性,JVM 将保护具有适当动态类型检查的调用,如其他地方所述。

字节代码生成器(包括编译器后端)需要为这些方法发出未转换的符号类型描述符。 确定符号链接的工具需要接受此类未转换的描述符,而无需报告链接错误。

<方法句柄与核心反射 API</h1 之间的 h1>> 互操作使用 API 中的java.lang.invoke.MethodHandles.Lookup Lookup工厂方法,核心反射 API 对象表示的任何类成员都可以转换为行为等效的方法句柄。 例如,可以使用反射 java.lang.reflect.Method Method 转换为方法句柄 java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect。 生成的方法处理通常提供对基础类成员的更直接且高效的访问。

作为一种特殊情况,当核心反射 API 用于查看此类中的签名多态方法 invokeExact 或普通 invoke 方法时,它们显示为普通的非多态方法。 它们的反光外观,如此 API java.lang.Class#getDeclaredMethod Class.getDeclaredMethod所查看,不受其特殊状态影响。 例如, java.lang.reflect.Method#getModifiers Method.getModifiers 将准确报告任何类似声明的方法所需的修饰符位,包括在这种情况下 nativevarargs 位。

与任何反射方法一样,可以通过调用这些方法(如果反射时 java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke)。 但是,此类反射调用不会导致方法处理调用。 如果传递了所需的参数(类型为单个 Object[]参数),则此类调用将忽略该参数,并将引发一个 UnsupportedOperationException

由于 invokevirtual 指令可以在任何符号类型描述符下本机调用方法句柄,因此这种反射视图与通过字节码对这些方法的正常表示冲突。 因此,这两种本机方法在经过反省时 Class.getDeclaredMethod只能被视为占位符。

若要获取特定类型描述符的调用方方法,请使用 java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvokerjava.lang.invoke.MethodHandles#invoker MethodHandles.invokerjava.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual对于任何指定的类型描述符,API 还可以返回调用或纯invoke方法invokeExact句柄。

<方法句柄与 Java generics</h1 之间的 h1>> 互操作可以在使用Java泛型类型声明的方法、构造函数或字段上获取。 与核心反射 API 一样,方法句柄的类型将从源级别类型的擦除构造。 调用方法句柄时,其参数的类型或返回值强制转换类型可以是泛型类型或类型实例。 如果发生这种情况,编译器将在为指令构造符号类型描述符时将其擦除替换这些 invokevirtual 类型。

方法句柄在Java参数化(泛型)类型方面不表示其类似函数的类型,因为类似函数的类型与参数化Java类型之间存在三种不匹配。 <ul><li>方法类型范围涵盖所有可能的 arities,从无参数到允许参数的最大数目。 泛型不是可变的,因此不能表示这种情况。</li li><>方法类型可以指定基元类型的参数,Java泛型类型不能超出范围。</li li><>基于方法句柄(组合器)的更高顺序函数通常跨各种函数类型(包括多个 ar 的函数类型)泛型。 无法使用Java类型参数来表示这种泛型。</li></ul>

<h1>“maxarity”>Arity limits</h1> JVM 对所有方法和构造函数施加任何类型的绝对限制为 255 个堆积参数。 在某些情况下,此限制可能更具限制性: <ul><li>A longdouble 参数计数(出于 arity 限制目的)作为两个参数槽。 <li>非静态方法为调用该方法的对象使用额外的参数。 <li>A 构造函数使用正在构造的对象的额外参数。 <li>自方法句柄'除任何非虚拟接收器对象外,s invoke 方法(或其他签名多态方法)是非虚拟方法,它还使用方法句柄本身的额外参数。 </ul> 这些限制意味着某些方法句柄无法创建,仅仅是因为堆栈参数上的 JVM 限制。 例如,如果静态 JVM 方法完全接受 255 个参数,则无法为其创建方法句柄。 尝试使用不可能的方法类型创建方法句柄会导致 。IllegalArgumentException 具体而言,方法句柄's 类型不能具有精确最大值为 255 的 arity。

Java文档java.lang.invoke.MethodHandle

本页的某些部分是根据 创建和共享的工作进行的修改,并根据 许可证中所述的条款使用。

构造函数

名称 说明
MethodHandle(IntPtr, JniHandleOwnership)

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

属性

名称 说明
Class

返回此 Object的运行时类。

(继承自 Object)
Handle

基础 Android 实例的句柄。

(继承自 Object)
IsVarargsCollector

确定此方法句柄是否支持 #asVarargsCollector 变量 arity 调用。

JniIdentityHashCode

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
JniPeerMembers

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

PeerReference

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
ThresholdClass

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

ThresholdType

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

方法

名称 说明
AsCollector(Class, Int32)

<使 em>数组收集</em> 方法句柄接受给定数量的尾随位置参数,并将其收集到数组参数中。

AsCollector(Int32, Class, Int32)

<使 em>数组收集</em> 方法句柄接受从给定位置开始的给定数量的位置参数,并将其收集到数组参数中。

AsFixedArity()

<将 em>固定 arity</em> 方法句柄设置为等效于当前方法句柄。

AsSpreader(Class, Int32)

<使 em>数组分布</em> 方法句柄接受尾随数组参数,并将其元素分散为位置参数。

AsSpreader(Int32, Class, Int32)

<使 em>数组分布</em> 方法句柄接受位于给定位置的数组参数,并将其元素分散为位置参数代替数组。

AsType(MethodType)

生成适配器方法句柄,该句柄将当前方法句柄的类型改编为新类型。

AsVarargsCollector(Class)

<使 em>变量 arity</em> 适配器能够接受任意数量的尾随位置参数并将其收集到数组参数中。

BindTo(Object)

将值 x 绑定到方法句柄的第一个参数,而无需调用它。

Clone()

创建并返回此对象的副本。

(继承自 Object)
Dispose()

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
Dispose(Boolean)

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
Equals(Object)

指示其他对象是否“等于”此对象。

(继承自 Object)
GetHashCode()

返回对象的哈希代码值。

(继承自 Object)
Invoke(Object[])

调用方法句柄,允许任何调用方类型描述符,并选择性地对参数和返回值执行转换。

InvokeExact(Object[])

调用方法句柄,允许任何调用方类型描述符,但需要完全类型匹配。

InvokeWithArguments(IList<Object>)

执行变量 arity 调用,将给定数组中的参数传递给方法句柄,就好像通过调用站点中的不表达式 #invoke invoke (仅提及类型 Object)以及其 arity 是参数数组的长度。

InvokeWithArguments(Object[])

执行变量 arity 调用,将给定列表中的参数传递给方法句柄,就好像通过调用站点中的不表达式 #invoke invoke (仅提及类型 Object)以及参数列表的 arity 是参数列表的长度。

JavaFinalize()

当垃圾回收确定不再引用该对象时,由对象上的垃圾回收器调用。

(继承自 Object)
Notify()

唤醒正在等待此对象的监视器的单个线程。

(继承自 Object)
NotifyAll()

唤醒正在等待此对象的监视器的所有线程。

(继承自 Object)
SetHandle(IntPtr, JniHandleOwnership)

设置 Handle 属性。

(继承自 Object)
ToArray<T>()

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
ToString()

返回对象的字符串表示形式。

(继承自 Object)
Type()

报告此方法句柄的类型。

UnregisterFromRuntime()

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
Wait()

使当前线程等待,直到唤醒它,通常是通过 em 通知/em< 或 >em<interrupted>/em<。><>

(继承自 Object)
Wait(Int64, Int32)

使当前线程等待直到唤醒,通常是通过 <em>通知</em> 或 <em interrupted>/em<>,或直到经过一定数量的实时。

(继承自 Object)
Wait(Int64)

使当前线程等待直到唤醒,通常是通过 <em>通知</em> 或 <em interrupted>/em<>,或直到经过一定数量的实时。

(继承自 Object)
WithVarargs(Boolean)

如果布尔标志为 true,则调整此方法句柄 #asVarargsCollector 变量 arity,否则 #asFixedArity 固定 arity。

显式接口实现

名称 说明
IJavaPeerable.Disposed()

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
IJavaPeerable.DisposeUnlessReferenced()

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
IJavaPeerable.Finalized()

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
IJavaPeerable.JniManagedPeerState

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
IJavaPeerable.SetJniIdentityHashCode(Int32)

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
IJavaPeerable.SetJniManagedPeerState(JniManagedPeerStates)

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)
IJavaPeerable.SetPeerReference(JniObjectReference)

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

(继承自 Object)

扩展方法

名称 说明
GetJniTypeName(IJavaPeerable)

获取实例 self类型的 JNI 名称。

JavaAs<TResult>(IJavaPeerable)

尝试强制self键入TResult,检查强制是否在Java端有效。

JavaCast<TResult>(IJavaObject)

执行 Android 运行时检查的类型转换。

JavaCast<TResult>(IJavaObject)

方法句柄是类型化、直接可执行对基础方法、构造函数、字段或类似低级别操作的引用,具有参数或返回值的可选转换。

TryJavaCast<TResult>(IJavaPeerable, TResult)

尝试强制self键入TResult,检查强制是否在Java端有效。

适用于