Lambda表达式可以访问超出其范围的类的私有方法吗? [英] Can Lambda expressions access private methods of classes outside their scope?

查看:55
本文介绍了Lambda表达式可以访问超出其范围的类的私有方法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to gain reflective access to java.lang.String's package private constructor.

也就是说,这个:

/*
* 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;
}

为此创建一个 MethodHandle 很简单,因此也要调用它. 直接使用反射功能也是如此.

Creating a MethodHandle for it is simple enough, and so is invoking it. The same is true for using Reflection directly.

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

But I'm curious whether it's possible to directly call the constructor via functional interfaces.

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

27602758 touches on a somewhat similar issue, but the solutions provided do not appear to work in this case.

下面的测试用例编译没有问题.除实际的接口调用外,其他所有操作均有效.

The test case below compiles without issues. Everything works, except for the actual interface invocation.

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();
    }
}

具体说明

shared.create("foo".toCharArray(), true)

引发以下错误:

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)

尽管表面上授予了访问权限,为什么仍会引发此错误?

谁能提出一个解释,说明为什么生成的接口无法访问其所有组件都可以访问的方法?

Can anyone come up with an explanation for why the generated interface has no access to a method that all of its components have access to?

是否有一种解决方案或可行的替代方案真正适用于此特定用例,而无需恢复为纯粹的Reflection或 MethodHandles ?

Is there a solution or a viable alternative that actually works for this particular use case, without reverting to pure Reflection or MethodHandles?

因为我很困惑.

推荐答案

问题是您覆盖了要信任的查找对象,因此其对Stringprivate方法的访问将通过查找过程和lambda元工厂,但它仍然绑定到您的Test类,因为那是通过MethodHandles.lookup()创建查找对象的类,并且所生成的类将位于同一上下文中.当涉及到这些生成的类时,JVM对于可访问性非常慷慨,但是显然,不接受从存在于应用程序类上下文中的类访问引导类java.lang.Stringprivate成员.

The problem is that you override the lookup object to be trusted, so its access to a private method of String will pass the lookup procedure and lambda meta factory, but its still bound to your Test class as that’s the class which created the lookup object via MethodHandles.lookup() and the generated class will live in the same context. The JVM is quite generous regarding accessibility when it comes to these generated classes but apparently, accessing a private member of the bootstrap class java.lang.String from a class living in the context of your application class is not accepted.

您可以通过例如MethodHandles.lookup() .in(String.class)(然后对其进行修补以使其具有private或可信"访问权限),但是,您将遇到另一个问题:生活在java.lang.String上下文中(或仅在引导加载程序的上下文中)的类不会有权访问您的自定义interface StringCreator并且无法实现.

You can get a lookup object living in an appropriate context via, e.g. MethodHandles.lookup() .in(String.class) (and then patching it to have private or "trusted" access), but then, you will get another problem: a class living in the context of java.lang.String (or just in the bootstrap loader’s context) will not have access to your custom interface StringCreator and can’t implement it.

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

The only solution is to use a lookup object living in the context of String and implementing one of the existing generic interfaces, accessible from the bootstrap class loader:

import java.lang.invoke.*;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.util.function.BiFunction;

public class Test {
    public static void main(String[] args) throws Throwable {
        // Reflectively generate a TRUSTED Lookup for the String class
        Lookup caller = MethodHandles.lookup().in(String.class);
        Field modes = Lookup.class.getDeclaredField("allowedModes");
        modes.setAccessible(true);
        modes.setInt(caller, -1);   // -1 == Lookup.TRUSTED
        // create handle for shared String constructor
        MethodHandle constructor = caller.findConstructor(
            String.class, MethodType.methodType(void.class, char[].class, boolean.class)
        );
        // generate interface implementation for constructor
        BiFunction<char[],Boolean,String> shared=getStringCreator(caller, constructor);

        // test if the construcor is correctly accessed
        char[] chars = "foo".toCharArray();
        String s = shared.apply(chars, true);
        chars[0] = 'b'; chars[1] = 'a'; chars[2] = 'r';// modify array contents
        System.out.println(s); // prints "bar"
        chars[0] = '1'; chars[1] = '2'; chars[2] = '3';
        System.out.println(s); // prints "123"
    }
    private static BiFunction<char[],Boolean,String> getStringCreator(
            Lookup caller, MethodHandle handle) throws Throwable {
        CallSite callSite = LambdaMetafactory.metafactory(
            caller,
            "apply",
            MethodType.methodType(BiFunction.class),
            handle.type().generic(),
            handle,
            handle.type()
        );
        return (BiFunction) callSite.getTarget().invokeExact();
    }
}

这篇关于Lambda表达式可以访问超出其范围的类的私有方法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆