Lambda表达式可以访问其范围之外的类的私有方法吗?

Jan*_*nik 7 java reflection lambda

我想获得对java.lang.String的包私有构造函数的反射访问.

即,这一个:

/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}
Run Code Online (Sandbox Code Playgroud)

为它创建一个MethodHandle很简单,所以调用它也是如此.直接使用Reflection也是如此.

但我很好奇是否可以通过功能接口直接调用构造函数.

27602758触及了一个类似的问题,但提供的解决方案在这种情况下似乎不起作用.

下面的测试用例编译没有问题.一切都有效,除了实际的接口调用.

package test;

import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;

public class Test {

    // Creates a new String that shares the supplied char[]
    private static interface StringCreator {

        public String create(char[] value, boolean shared);
    }

    // Creates a new conventional String
    private static String create(char[] value, boolean shared) {
        return String.valueOf(value);
    }

    public static void main(String[] args) throws Throwable {
        // Reflectively generate a TRUSTED Lookup for the calling class
        Lookup caller = MethodHandles.lookup();
        Field modes = Lookup.class.getDeclaredField("allowedModes");
        modes.setAccessible(true);
        modes.setInt(caller, -1);   // -1 == Lookup.TRUSTED

        // create handle for #create()
        MethodHandle conventional = caller.findStatic(
            Test.class, "create", MethodType.methodType(String.class, char[].class, boolean.class)
        );
        StringCreator normal = getStringCreator(caller, conventional);
        System.out.println(
            normal.create("foo".toCharArray(), true)
        // prints "foo"
        );

        // create handle for shared String constructor
        MethodHandle constructor = caller.findConstructor(
            String.class, MethodType.methodType(void.class, char[].class, boolean.class)
        );
        // test directly if the construcor is correctly accessed
        char[] chars = "foo".toCharArray();
        String s = (String) constructor.invokeExact(chars, true);
        chars[0] = 'b'; // modify array contents
        chars[1] = 'a';
        chars[2] = 'r';
        System.out.println(
            s
        // prints "bar"
        );

        // generate interface for constructor
        StringCreator shared = getStringCreator(caller, constructor);
        System.out.println(
            shared.create("foo".toCharArray(), true)
        // throws error
        );
    }

    // returns a StringCreator instance
    private static StringCreator getStringCreator(Lookup caller, MethodHandle handle) throws Throwable {
        CallSite callSite = LambdaMetafactory.metafactory(
            caller,
            "create",
            MethodType.methodType(StringCreator.class),
            handle.type(),
            handle,
            handle.type()
        );
        return (StringCreator) callSite.getTarget().invokeExact();
    }
}
Run Code Online (Sandbox Code Playgroud)

特别是指令

shared.create("foo".toCharArray(), true)
Run Code Online (Sandbox Code Playgroud)

抛出以下错误:

Exception in thread "main" java.lang.IllegalAccessError: tried to access method java.lang.String.<init>([CZ)V from class test.Test$$Lambda$2/989110044 at test.Test.main(Test.java:59)
Run Code Online (Sandbox Code Playgroud)

尽管表面上被授予访问权限,为什么仍然会抛出此错误?

任何人都可以解释为什么生成的接口无法访问其所有组件都可以访问的方法吗?

是否存在实际适用于此特定用例的解决方案或可行替代方案,而无需恢复为纯Reflection或MethodHandles

因为我很难过.

Hol*_*ger 3

问题是您重写了要信任的查找对象,因此它对 的private方法的访问String将传递查找过程和 lambda 元工厂,但它仍然绑定到您的Test类,因为 \xe2\x80\x99s 是创建通过查找对象MethodHandles.lookup()和生成的类将位于同一上下文中。当涉及到这些生成的类时,JVM 在可访问性方面相当慷慨,但显然,不接受从应用程序类上下文中的类访问private引导类的成员。java.lang.String

\n\n

MethodHandles.lookup() .in(String.class)您可以通过例如(然后修补它以具有或 \xe2\x80\x9ctrusted\xe2\x80\x9d 访问权限)获得生活在适当上下文中的查找对象private,但是,您会遇到另一个问题:生活在的类的上下文java.lang.String(或仅在引导加载程序\xe2\x80\x99s 上下文中)将无法访问您的自定义interface StringCreator,并且可以\xe2\x80\x99t 实现它。

\n\n

唯一的解决方案是使用存在于现有泛型上下文中String并实现现有泛型之一的查找对象interface,可从引导类加载器访问:

\n\n
import java.lang.invoke.*;\nimport java.lang.invoke.MethodHandles.Lookup;\nimport java.lang.reflect.Field;\nimport java.util.function.BiFunction;\n\npublic class Test {\n    public static void main(String[] args) throws Throwable {\n        // Reflectively generate a TRUSTED Lookup for the String class\n        Lookup caller = MethodHandles.lookup().in(String.class);\n        Field modes = Lookup.class.getDeclaredField("allowedModes");\n        modes.setAccessible(true);\n        modes.setInt(caller, -1);   // -1 == Lookup.TRUSTED\n        // create handle for shared String constructor\n        MethodHandle constructor = caller.findConstructor(\n            String.class, MethodType.methodType(void.class, char[].class, boolean.class)\n        );\n        // generate interface implementation for constructor\n        BiFunction<char[],Boolean,String> shared=getStringCreator(caller, constructor);\n\n        // test if the construcor is correctly accessed\n        char[] chars = "foo".toCharArray();\n        String s = shared.apply(chars, true);\n        chars[0] = \'b\'; chars[1] = \'a\'; chars[2] = \'r\';// modify array contents\n        System.out.println(s); // prints "bar"\n        chars[0] = \'1\'; chars[1] = \'2\'; chars[2] = \'3\';\n        System.out.println(s); // prints "123"\n    }\n    private static BiFunction<char[],Boolean,String> getStringCreator(\n            Lookup caller, MethodHandle handle) throws Throwable {\n        CallSite callSite = LambdaMetafactory.metafactory(\n            caller,\n            "apply",\n            MethodType.methodType(BiFunction.class),\n            handle.type().generic(),\n            handle,\n            handle.type()\n        );\n        return (BiFunction) callSite.getTarget().invokeExact();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n