方法句柄 - 它到底是什么?

58
我正在学习JDK 1.7的新特性,但是我不明白MethodHandle的设计目的是什么?我理解(直接)调用静态方法(并使用Core Reflection API,在这种情况下很简单)。我也理解(直接)调用虚拟方法(非静态、非final)(并使用Core Reflection API,需要通过类的层次结构obj.getClass().getSuperclass())。调用非虚拟方法可以被视为前者的特殊情况。
是的,我知道有一个重载问题。如果要调用方法,必须提供精确的签名。你不能轻松地检查重载的方法。
但是,MethodHandle是什么?反射API允许您“查看”对象内部而不做任何假设(如实现接口)。您可以检查对象以达到某种目的。但是MethodHandle是为什么设计的?我应该在什么时候使用它?

更新:我现在正在阅读http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html这篇文章。根据它的说法,主要目标是简化运行在JVM之上的脚本语言的生活,而不是针对Java语言本身。

更新-2:我已经完成阅读上面的链接,以下是一些引用:

“JVM将成为构建动态语言的最佳虚拟机,因为它已经是一种动态语言虚拟机。InvokeDynamic通过将动态语言提升为一流的JVM公民,将证明这一点。”使用反射调用方法效果很好...除了一些问题。方法对象必须从特定类型中检索,并且无法以通用方式创建。......反射调用比直接调用要慢得多。多年来,JVM在使反射调用变快方面做得非常好。现代JVM实际上会在幕后生成大量代码,以避免旧JVM处理的许多开销。但简单的事实是,通过任意数量的层进行反射访问始终比直接调用要慢,部分原因是完全泛型化的“invoke”方法必须检查和重新检查接收器类型、参数类型、可见性和其他详细信息,还因为参数必须全部作为对象提供(因此基元类型被装箱为对象),并且必须作为数组提供以涵盖所有可能的元数(因此参数被装箱为数组)。性能差异对于执行少量反射调用的库可能并不重要,特别是如果这些调用主要是为了动态设置静态内存结构,以便可以对其进行正常调用。但是在动态语言中,每个调用都必须使用这些机制,这对性能造成了严重影响。”

http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html

因此,对于Java程序员来说,它基本上是无用的。我是正确的吗?从这个角度来看,它只能被认为是核心反射API的替代方式。

更新-2020年:实际上,MethodHandle可以被认为是核心反射API的更强大的替代方案。从JDK 8开始,还有使用它的Java语言功能。


不,绝对不是这样的。那篇文章已经超过3年了。许多框架开发人员正在密切关注MH和invokedynamic,并寻找使用它们来帮助开发人员的方法。 - kittylyst
你能提供这样一个使用的例子吗?使用 MethodHandle 可以做什么,而 Core Reflection API 不可能做到呢? - alexsmail
1
例如,与安全管理器交互。在安全管理器下,setAccessible()会让你死亡,而Lookup机制是完全安全的。Lookup还允许您在可以看到它的上下文中创建MH,然后将引用传递给非特权上下文。 - kittylyst
2
实际上,我从未遇到过使用 setAccessible() 函数的问题。理论上,我可以使用 AccessController.doPrivileged(new PrivilegedAction()) 的惯用语法。 - alexsmail
也许看一下 https://en.cppreference.com/w/cpp/utility/functional/function 会有所帮助。 - Martin Meeser
4个回答

35

您可以使用MethodHandles来进行函数柯里化、更改参数类型和顺序。

MethodHandles能够处理方法和字段。

MethodHandles的另一个技巧是直接使用基本类型,而不是通过包装器间接使用。

由于JVM中有更多的直接支持,MethodHandles比使用反射更快,例如他们可以被内联。它使用了新的invokedynamic指令。


6
此外,在调用时,方法句柄的堆栈跟踪比反射更好。不过,缺点是您无法通过反射调用方法句柄(但可以反过来)。 - mihi
9
MethodHandle 不使用 invokedynamic 指令。但是另一方面,invokedynamic 指令严重依赖于 MethodHandle - Holger
2
@Peter 你说的“Another trick which MethodHandles do is use primitive direct (rather than via wrappers)”是什么意思? - Geek
4
如果你使用一个方法,它将包装参数和返回值。例如,在 double method(double d) { return d; } 上调用它至少会创建两个对象。但是在使用 MethodHandles 时,它足够智能,可以在调用时去除包装参数或返回值的必要性,尽管从概念上讲它接受一个 Object... 并返回一个 Object - Peter Lawrey
1
这是因为它不使用invokedynamic,而是使用专门为此创建的invokepolymorphic。如果您查看字节码,您会发现invokedynamic不仅存储原始的被忽略的签名,还存储您调用它时使用的签名。 - kb1000
实际上,在字节码中看起来有一个名为double MethodHandle.invokeExact(double)的方法。 - Johannes Kuhn

12

把MethodHandle看作是一种现代、更灵活、更类型安全的反射方式。

它目前处于生命周期的早期阶段,但随着时间的推移,它有潜力被优化,变得比反射更快——甚至可以变得像常规方法调用一样快。


1
Java的基本哲学是不仅提供一种可能的方法给Java程序员。我知道在JDK 1.5中引入了一些功能,但这些功能对Java程序员非常有用(好吧,我知道其中一些是有争议的),但是就我目前所看到的,MethodHandle功能对于普通的Java开发人员来说并不实用。 - alexsmail

11

java.lang.reflect.Method相对缓慢且在内存方面开销较大。方法句柄被认为是一种“轻量级”方式,用于传递指向JVM有机会优化的函数的指针。截至JDK8,方法句柄并未得到很好的优化,而Lambda表达式可能最初是基于类(如内部类)实现的。


@tackline,我知道java.lang.reflect.Method相对较慢。我也看到一些统计数据表明MehodHandle并不是那么快。所以,主要原因是性能吗?Lambda在Java中是做什么的?Lambda演算是一种完全不同的编程方法... - alexsmail

6
自从我提出这个问题已经过去将近9年了。JDK 14是最后一个大量使用MethodHandle的稳定版本... 我写了一系列关于invokedynamic的文章,链接在这里:https://alex-ber.medium.com/explaining-invokedynamic-introduction-part-i-1079de618512。下面摘录了其中相关的部分。
MethodHandle可以被认为是Core Reflection API更加强大的替代品。MethodHandle是一个对象,它存储有关方法(构造函数、字段或类似的低级操作)的元数据,例如方法的名称、方法签名等。对它的一种看法是指向方法的指针的目标(解引用的方法(构造函数、字段或类似的低级操作))。
Java代码可以创建一个直接访问任何方法、构造函数或字段的方法句柄,只要该代码可访问这些内容。这是通过称为MethodHandles.Lookup的反射性、基于能力的API完成的。例如,可以从Lookup.findStatic获取静态方法句柄。还有从核心反射API对象进行转换的方法,如Lookup.unreflect。
重要的是要理解Core Reflection API和MethodHandle之间的两个关键差异。
1. 使用MethodHandle,在构建时仅进行一次访问检查;而使用Core Reflection API,在每次调用invoke方法时都要进行访问检查(并且每次都会调用Securty Manager,降低性能)。
2. Core Reflection API的invoke方法是常规方法。在MethodHandle中,所有invoke*变体都是签名多态方法。
基本上,访问检查意味着您是否可以访问方法(构造函数、字段或类似的低级操作)。例如,如果该方法(构造函数、字段或类似的低级操作)是私有的,通常无法调用它(从字段获取值)。
与Reflection API相反,JVM可以完全透明地看到MethodHandles并尝试对其进行优化,因此具有更好的性能。
注意:使用MethodHandle还可以生成实现逻辑。有关详细信息,请参见 Dynamical hashCode implementation. Part V。https://alex-ber.medium.com/explaining-invokedynamic-dynamical-hashcode-implementation-part-v-16eb318fcd47

在MethodHandle中,所有的invoke*变量都是签名多态方法。MethodHandle.invokeWithArguments(两个重载)是普通方法。 - Johannes Kuhn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接