【发布时间】:2018-05-09 19:36:42
【问题描述】:
最近尝试通过java.lang.invoke库访问数组类型的clone方法。这被证明是不成功的。示例中的克隆方法是这样的:
int[] a = new int[]{1, 2, 3, 4};
int[] b = a.clone();
我希望为a.clone() 调用创建一个MethodHandle。这样生成的代码类似于:
int[] a = new int[]{1, 2, 3, 4};
int[] b = findCloneMethod().invoke(a);
我为所有其他方法调用设置了这个系统。但是,仅使用这种方法,系统就会失败。
这个问题与java.lang.invoke 库有关,不是java.lang.reflect 库。
问题描述
引入了以下示例代码来显示此行为。
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class Main {
void test() {
final MethodHandles.Lookup caller = MethodHandles.lookup();
final Class<?> refc = int[].class;
final String name = "clone";
final MethodType type = MethodType.methodType(Object.class);
final MethodHandle mh = findVirtual(caller, refc, name, type);
System.out.println("Lookup: " + caller);
System.out.println("mh: " + mh);
}
public static void main(String[] args) {
new Main().test();
}
private MethodHandle findVirtual(final MethodHandles.Lookup caller, final Class<?> refc, final String name, final MethodType type) {
try {
return caller.findVirtual(refc, name, type);
} catch (final Throwable t) {
t.printStackTrace();
return null;
}
}
}
样本输出:
查找:主要
mh: MethodHandle(Main)Object
输出是有意义的。来自Main 范围的调用者只能看到Main 的超类Object 中的clone 方法。即使int[] 被用作引用类,这个方法也是不可见的。这成为一个问题,因为如果使用此调用站点,JVM 会尝试将 int[] 转换为 Main。
通过将引用类型引入int[],可以预期int[] 类型中的clone 方法可以公开访问。因此,当文档建议时:
方法句柄的类型将是方法的类型,接收者类型(通常是 refc)在前面。 1
mh 的类型将是 (int[])Object。
尝试的解决方案:
由于查找caller 正在通过隐藏正确的clone 方法进行干扰,因此尝试使用公共查找:
final MethodHandles.Lookup caller = MethodHandles.publicLookup();
但是,这失败了。对Object#clone() 签名的简单检查说明了原因:
protected Object clone() throws CloneNotSupportedException { ...
方法是protected。这不适用于数组类型,因为实现使该方法公开可用。
这方面的一个例子是尝试访问Object#equals 方法。这显示为三个独立的参考类:
final MethodHandles.Lookup caller = MethodHandles.publicLookup();
final Class<?> refc = Object.class;
final String name = "equals";
final MethodType type = MethodType.methodType(boolean.class, Object.class);
查找:java.lang.Object/public
mh: MethodHandle(Object,Object)boolean
然后使用Main.class作为引用类:
final Class<?> refc = Main.class;
查找:java.lang.Object/public
mh: MethodHandle(Main,Object)boolean
然后使用int[].class作为引用类:
final Class<?> refc = int[].class
查找:主要
mh: MethodHandle(int[],Object)boolean
这是预期的行为,显然可以流畅地工作。在第一种情况下,引用类是Object,它返回Object#equals 方法句柄。第二个,引用类是Main,它返回Main#equals方法句柄。最后,引用类是int[].class,它返回了int[]#equals 方法句柄。
虽然不使用int[]#clone 方法维护这种奇偶校验。由于clone 方法是受保护的,但对于数组是公共的,所以找不到它。对我来说,这似乎是一个错误。数组clone 方法应通过publicLookup 公开。返回的方法句柄类型可以是:MethodHandle(Object)Object。
一旦检索到,MethodHandle#asType 就可以用于更正类型。在这种情况下,它将被转换为MethodHandle(int[])Object。
如果没有公开可用的Object#clone 方法,至少对于数组类型来说,这似乎是不可能的。
此外,这似乎是唯一可能发生这种情况的方法,因为数组类型仅扩展/实现 3 个类:Object、Cloneable 和 Serializable。唯一的其他方法是Object#finalize,它也受到保护;但是,数组类型不会公开此方法。因此clone 是唯一失败的方法。
tl;博士:
数组clone 方法,例如Object[]#clone(),不能通过公共查找公开访问。这是因为Object#clone 是protected;但是,数组类型使此方法公开可用。由于此可见性问题,尝试通过引用类访问数组 clone 失败。因此,无法为此方法生成MethodHandle。但是,在 Object#equals 等公共方法上尝试相同的技术对于数组类型效果很好。
我宁愿避免反射访问,因为方法可以通过简单地更改查找的信任级别来检索它。
有没有办法为数组clone方法生成MethodHandle?
注意:我确实了解java.lang.invoke 的正确用法,我不打算用它来代替java.lang.reflect。
【问题讨论】:
-
你用的是什么版本的java?我在使用
lookup()时遇到了与(Main)Object相同的问题,但它使用publicLookup()找到了正确的方法。实际上,这似乎也适用于 1.8:ideone.com/vMmWMr -
@JornVernee 我更新了我的 Java 版本,这似乎工作正常。我最初运行的是 1.8(忘记检查次要版本)。此外,指定的引用类型是错误的
Object.class。第二个问题产生了一个不同的错误,所以我猜这是两者的结合。我会将问题留待任何答案,如果没有人这样做,我将在稍后总结社区答案。谢谢!
标签: java jvm invokedynamic