通过公共查找检索数组克隆方法句柄

时间:2018-05-09 19:36:42

标签: java jvm invokedynamic

最近,我尝试通过clone库访问数组类型的java.lang.invoke方法。事实证明这是不成功的。样本中的克隆方法就是这样的:

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范围的调用者仅在clone的超类Main中看到Object方法。即使int[]用作引用类,此方法也不可见。这成为一个问题,因为如果使用此调用站点,JVM会尝试将int[]强制转换为Main

通过将引用类型引入为int[],可以预期类型clone中的int[]方法可公开访问。因此,当文档建议:

  

方法句柄的类型将是方法的类型,前缀为接收器类型(通常为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个类:ObjectCloneableSerializable。唯一的另一种方法是Object#finalize,它也受到保护;但是,数组类型不会使此方法公开。因此,clone是唯一失败的方法。

TL; DR:

数组clone方法(例如Object[]#clone())无法通过公共查找公开访问。这是因为Object#cloneprotected;但是,数组类型使此方法公开可用。由于此可见性问题,尝试通过引用类访问数组clone失败。因此,无法为此方法生成MethodHandle。但是,在Object#equals等公共方法上尝试相同的技术可以很好地适用于数组类型。

我更愿意避免使用反射访问,因为只需更改查找的信任级别即可检索它。

有没有办法为数组MethodHandle方法生成clone

注意:我确实理解java.lang.invoke的正确用法,我不打算用它来代替java.lang.reflect

1 个答案:

答案 0 :(得分:2)

在JSR 292的早期实现中存在一个错误JDK-8001105,其中<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <form> <input type="text" id="name" placeholder="Name"> <input type="button" class="add-row" value="Add Row"> </form> <table border="1"> <thead> <tr> <th>Name</th><th>&nbsp;</th> </tr> </thead> <tbody> <tr> <td>Peter Parker</td><td style="cursor:pointer;" onclick="del(this);">Delete</td> </tr> </tbody> </table>不适用于数组的findVirtual方法。但是,这个问题很久以前就已经解决了。

因此,在最近的JDK 8 clonefindVirtual如果在clone上调用则可以正常工作。{
您可以在JDK source code中找到一个特殊情况:

publicLookup()