Chcę uzyskać dostęp odblaskowy do prywatnego zestawu konstruktora pakietu java.lang.String.Czy wyrażenia Lambda mogą korzystać z prywatnych metod zajęć poza ich zakresem?
Mianowicie, ten jeden:
/*
* 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;
}
Tworzenie MethodHandle bo jest na tyle prosta, a więc powołuje go. To samo dotyczy korzystania z funkcji Odbicie bezpośrednio.
Ale jestem ciekawy, czy możliwe jest bezpośrednie wywołanie konstruktora za pomocą funkcjonalnych interfejsów.
27602758 dotyczy nieco podobnego problemu, ale dostarczone rozwiązania nie działają w tym przypadku.
Poniższy przykładowy test kompiluje się bez problemów. Wszystko działa, z wyjątkiem faktycznego wywołania interfejsu.
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 dyspozycja
shared.create("foo".toCharArray(), true)
wyrzuca następujący błąd:
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)
Dlaczego ten błąd nadal wyrzucane, mimo pozornie jest przyznany dostęp?
Czy ktoś może wyjaśnić, dlaczego wygenerowany interfejs nie ma dostępu do metody, do której mają dostęp wszystkie jej składniki?
Czy istnieje rozwiązanie lub realna alternatywa, która faktycznie działa w tym konkretnym przypadku użycia, bez powrotu do czystej refleksji lub MethodHandles?
Bo jestem zakłopotany.
nie może odpowiedzieć na pytanie ostatecznie rzeczywisty , ale czysta refleksja nie jest tutaj straszna. Metoda/Konstruktor/itd. są zasadniczo klasami proxy wokół akcesora z wygenerowanym kodem bajtowym. Tworzenie ich jest operacją wagi ciężkiej, ale inwokacja nie jest. 'Boolean' zostaje zapakowany, ale poza tym jest tak samo jak użycie' new', przynajmniej w implementacji Sun/OpenJDK. Reflection omija błąd dostępu, generując accessor tak, jakby znajdował się w zasięgu, do którego należy członek, co, jak sądzę, nie robi LambdaMetaFactory. – Radiodef
@Radiodef Dziękujemy za szczegóły. Jeśli nie znajdziesz rozwiązania lambda, po prostu użyję metody MethodHandle. Jestem w większości ciekawy, dlaczego test zachowuje się tak jak on. –