2016-03-23 6 views
6

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.

+1

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

+0

@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. –

Odpowiedz

2

Problemem jest to, że zastąpi obiekt odnośnika można ufać, więc jego dostęp do private metody String przejdzie procedurę przeglądową i lambda meta fabryki, ale jest nadal zobowiązany do klasy Test jak to jest klasa, która utworzona obiekt wyszukiwania przez MethodHandles.lookup(), a wygenerowana klasa będzie żyła w tym samym kontekście. JVM jest dość hojny pod względem dostępności, jeśli chodzi o te wygenerowane klasy, ale najwyraźniej dostęp do elementu klasy bootstrap od klasy żyjącej w kontekście klasy aplikacji nie jest akceptowany.

Możesz otrzymać obiekt wyszukiwania żyjący w odpowiednim kontekście za pośrednictwem np. MethodHandles.lookup() .in(String.class) (a następnie łata, aby uzyskać private lub "zaufany" dostęp), ale wtedy pojawi się kolejny problem: klasa żyjąca w kontekście java.lang.String (lub tylko w kontekście programu ładującego bootstrap) nie będzie miała dostępu do Twojego niestandardowego interface StringCreator i nie może go wdrożyć.

Jedynym rozwiązaniem jest użycie obiektu odnośnika mieszkających w kontekście String i realizacji jednego z istniejących generycznych interface S, dostępny z bootstrap klasy ładowarka:

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

dziękuję za szczegółowe wyjaśnienie i za działające rozwiązanie! Nie wyobrażam sobie, że istnieje sposób, aby taki odnośnik dowiedział się o klasach poza programem ładującym klasy bootstrap? –