3

最近尝试通过库来访问clone数组类型的方法。java.lang.invoke这被证明是不成功的。示例中的克隆方法是这样的:

int[] a = new int[]{1, 2, 3, 4};
int[] b = a.clone();

我希望MethodHandlea.clone()通话创建一个。这样生成的代码类似于:

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:方法句柄(主)对象


输出是有道理的。范围内的调用者Main只能看到的超类clone中的方法MainObject。即使int[]用作参考类,此方法也是不可见的。这成为一个问题,因为如果使用此调用站点,JVM 会尝试强制转换int[]Main.

通过将引用类型引入为int[],人们会期望clonetype 中的方法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:方法句柄(对象,对象)布尔

然后Main.class用作参考类:

final Class<?> refc = Main.class;

查找:java.lang.Object/public

mh:方法句柄(主要,对象)布尔

然后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;博士:

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


我宁愿避免反射访问,因为方法可以通过简单地更改查找的信任级别来检索它。

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


注意:我确实了解 的正确用法,java.lang.invoke并且我不打算将其用作java.lang.reflect.

4

1 回答 1

3

在 JSR 292 的早期实现中有一个错误JDK-8001105findVirtual不适用于数组的clone方法。不过,这个问题很久以前就已经解决了。

因此,在最近的 JDK 8findVirtual中,clone如果调用了publicLookup(). 您可能会在JDK 源代码
中找到一个特殊情况:

        if (Modifier.isProtected(mods) &&
                refKind == REF_invokeVirtual &&
                m.getDeclaringClass() == Object.class &&
                m.getName().equals("clone") &&
                refc.isArray()) {
            // The JVM does this hack also.
            // (See ClassVerifier::verify_invoke_instructions
            // and LinkResolver::check_method_accessability.)
            // Because the JVM does not allow separate methods on array types,
            // there is no separate method for int[].clone.
            // All arrays simply inherit Object.clone.
            // But for access checking logic, we make Object.clone
            // (normally protected) appear to be public.
            // Later on, when the DirectMethodHandle is created,
            // its leading argument will be restricted to the
            // requested array type.
            // N.B. The return type is not adjusted, because
            // that is *not* the bytecode behavior.
            mods ^= Modifier.PROTECTED | Modifier.PUBLIC;
        }
于 2018-05-09T21:34:10.877 回答