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

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

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

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

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

用户不能对方法句柄进行子类化。 实现可能 (,也可能不) 创建可通过 操作查看java.lang.Object#getClass Object.getClass的内部MethodHandle子类。 程序员不应从其特定类中得出有关方法句柄的结论,因为方法句柄类层次结构 (是否有任何) 可能会不时更改,或者可能在不同的供应商的实现之间发生更改。

<h1>方法句柄编译</h1> Java 方法调用表达式命名 invokeExactinvoke 可以从 Java 源代码调用方法句柄。 从源代码的角度来看,这些方法可以采用任何参数,并且其结果可以强制转换为任何返回类型。 正式地说,这是通过为调用程序方法 Object 提供返回类型和变量 arity Object 参数来实现的,但它们具有称为 <em>signature polymorphism</em> 的额外质量,它将这种调用自由直接连接到 JVM 执行堆栈。

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

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

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

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

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

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

类型匹配后,对 invokeExact 的调用直接并立即调用方法句柄的基础方法 (或其他行为,因为这种情况可能) 。

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

(<em 注意:</em> 调整后的方法句柄M2不可直接观察,因此不需要实现来具体>化它。)

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

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

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

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

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

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

<h1>“sigpoly”>签名多态性</h1> 和 普通invokeinvokeExact异常编译和链接行为由术语 <em>signature polymorphism</em> 引用。 如 Java 语言规范中定义,签名多态方法是一种可以处理各种调用签名和返回类型的方法。

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

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

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

<h1>方法句柄和核心反射 API< 之间的互操作/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 方法时,它们显示为普通的非多态方法。 其反射外观(如 查看 java.lang.Class#getDeclaredMethod Class.getDeclaredMethod)不受它们在此 API 中的特殊状态影响。 例如, java.lang.reflect.Method#getModifiers Method.getModifiers 将准确报告任何类似声明的方法所需的修饰符位,包括在本例 native 中和 varargs 位。

与任何反射方法一样,这些方法在反射时 () 可以通过 调用 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.invoker。 API java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual 还能够返回方法句柄,以便对任何指定的类型描述符 调用 invokeExact 或纯 invoke

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

就 Java 参数化 (泛型) 类型而言,方法句柄不表示其类似函数的类型,因为类似函数的类型与参数化 Java 类型之间存在三种不匹配。 <ul><li>方法类型涵盖所有可能的参数,从无参数到允许的最大参数数。 泛型不是可变的,因此无法表示这一点。</li><li>方法类型可以指定基元类型的参数,Java 泛型类型无法涵盖这些参数。</li><li>方法句柄上的高阶函数 (组合器) 通常在各种函数类型(包括多个函数类型)中泛型。 不可能使用 Java 类型参数来表示这种泛型。</li></ul>

<h1>“maxarity”>Arity limits</h1> JVM 对所有方法和任何类型的构造函数施加 255 个堆叠参数的绝对限制。 在某些情况下,此限制可能看起来更严格: <ul><li>A longdouble 参数计数 (,以达到) 为两个参数槽。 <li>非静态方法使用调用方法的对象的额外参数。 <li>构造函数使用正在构造的对象的额外参数。 <li>由于方法处理’s invoke 方法 (或其他签名多态方法) 是非虚拟的,除了任何非虚拟接收方对象外,它还使用方法句柄本身的额外参数。 </ul> 这些限制意味着无法创建某些方法句柄,这仅仅是因为堆栈参数的 JVM 限制。 例如,如果静态 JVM 方法只接受 255 个参数,则无法为其创建方法句柄。 尝试使用不可能的方法类型创建方法句柄会导致 IllegalArgumentException。 具体而言,方法处理’s 类型不能具有确切的最大 255。

java.lang.invoke.MethodHandleJava 文档。

此页面的部分内容是基于 创建和共享的工作进行的修改,并根据 署名许可中所述的术语使用。

构造函数

MethodHandle(IntPtr, JniHandleOwnership)

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

属性

Class

返回此 Object的运行时类。

(继承自 Object)
Handle

基础 Android 实例的句柄。

(继承自 Object)
IsVarargsCollector

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

JniIdentityHashCode

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

(继承自 Object)
JniPeerMembers

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

PeerReference

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

(继承自 Object)
ThresholdClass

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

ThresholdType

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

方法

AsCollector(Class, Int32)

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

AsCollector(Int32, Class, Int32)

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

AsFixedArity()

<使固定>arity</em> 方法句柄,否则等效于当前方法句柄。

AsSpreader(Class, Int32)

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

AsSpreader(Int32, Class, Int32)

<使 em>array-spread</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 调用,将给定列表中的参数传递给方法句柄,就好像通过调用站点中的 inexact #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)

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

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

导致当前线程等待,直到它被唤醒,通常是通过 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)

扩展方法

JavaCast<TResult>(IJavaObject)

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

JavaCast<TResult>(IJavaObject)

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

GetJniTypeName(IJavaPeerable)

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

适用于