Ich möchte reflektierenden Zugriff auf java.lang.String 's Paket privaten Konstruktor erhalten.Können Lambda-Ausdrücke auf private Methoden von Klassen außerhalb ihres Geltungsbereichs zugreifen?
Nämlich dieses:
/*
* 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;
}
Erstellen eines MethodHandle es einfach genug ist, und so ist es aufgerufen wird. Dasselbe gilt für die direkte Verwendung von Reflection.
Aber ich bin gespannt, ob es möglich ist, den Konstruktor über funktionale Schnittstellen direkt aufzurufen.
27602758 berührt ein ähnliches Problem, aber die angebotenen Lösungen scheinen in diesem Fall nicht zu funktionieren.
Der Testfall unten kompiliert ohne Probleme. Alles funktioniert, bis auf den eigentlichen Schnittstellenaufruf.
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();
}
}
specficially die Anweisung
shared.create("foo".toCharArray(), true)
führt den folgenden Fehler:
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)
Warum wird dieser Fehler noch geworfen, trotz Zugang angeblich gewährt werden?
Kann jemand eine Erklärung dafür finden, warum die generierte Schnittstelle keinen Zugriff auf eine Methode hat, auf die alle Komponenten zugreifen können?
Gibt es eine Lösung oder eine praktikable Alternative, die tatsächlich für diesen speziellen Anwendungsfall funktioniert, ohne auf reine Reflexion oder MethodHandles zurückzugreifen?
Weil ich ratlos bin.
Kann nicht Ihre eigentliche Frage beantworten endgültig , aber reine Reflexion ist hier nicht schrecklich. Methode/Konstruktor/etc. sind im Wesentlichen Proxy-Klassen um einen Accessor mit generiertem Bytecode. Sie zu erstellen ist eine Schwergewichtsoperation, aber eine Invokation ist es nicht. Der 'boolean' wird eingerahmt, aber ansonsten ist er ungefähr so wie 'new', zumindest in der Sun/OpenJDK-Implementierung. Reflection umgeht den Zugriffsfehler, indem der Accessor so generiert wird, als befände er sich innerhalb des Bereichs, zu dem das Member gehört, was LambdaMetaFactory hier nicht tut. – Radiodef
@Radiodef Vielen Dank für die Details. Wenn keine Lambda-Lösung gefunden werden kann, verwende ich einfach einen MethodHandle. Ich bin meistens neugierig, warum sich der Test so verhält wie er. –