Внутренняя лямбда возвращается в Class.getDeclaredMethods ()? - PullRequest
5 голосов
/ 06 апреля 2020

Рассмотрим этот класс:

public class Handler
{
    private Supplier<Foo> foo;

    public void handle( Bar bar )
    {
        foo = () -> bar.getFoo();
    }
}

И рассмотрим этот фрагмент отражения, который хочет получить доступ к методу handle ().

for( Method method : Handler.class.getDeclaredMethods() )
{
    if ( method.getParameterCount() == 1 && Bar.class.isAssignableFrom( method.getParameterTypes()[0] ) )
    {
        // This is the method you are looking for
    }
}

Вместо поиска

  • public void Handler.handle(Bar)

Он находит

  • private Foo Handler.lambda$3(Bar)

Что, очевидно, затем вызывает исключение:

java.lang.IllegalAccessException: class HandlerService cannot access a member of class Handler with modifiers "private static"

Может кто-нибудь объяснить, что здесь происходит, пожалуйста?

Похоже, Java рассматривает лямбду внутри метода как объявленный метод верхнего уровня. Это новое (или даже ошибка) в Java 11?

1 Ответ

3 голосов
/ 07 апреля 2020

Вы должны быть осторожны с предположениями о членах скомпилированного класса.

Есть даже сгенерированные компилятором члены, которые являются частью доступного API, такие как конструктор по умолчанию или values() и valueOf(String) методы enum типов. Кроме того, скомпилированные конструкторы внутренних классов и типов enum могут иметь больше параметров, чем видно в исходном коде, и из-за стирания типа сигнатуры в скомпилированных методах могут отличаться от исходного кода.

Кроме того, могут быть разные члены syntheti c. От Java 1.1 до Java 10 вложенные классы могут обращаться к закрытым членам друг друга через вспомогательные методы syntheti c (которые устарели в Java 11). Кроме того, переопределение методов обобщенных c классов или использование ковариантных возвращаемых типов может привести к генерации синтетического c метода моста.

И это еще не все.

Следующая программа

import java.util.Arrays;
import java.util.stream.Stream;

public enum ShowSyntheticMembers {
    ;
    public static void main(String[] args) {
        Stream.of(ShowSyntheticMembers.class, Inner.class)
            .flatMap(cl -> Stream.concat(Arrays.stream(cl.getDeclaredFields()),
                                         Arrays.stream(cl.getDeclaredMethods())))
            .forEach(System.out::println);
    }
    private boolean x;
    class Inner {
        protected String clone() {
            assert x;
            return "";
        }
    }
}

печатается при компиляции с JDK 11:

private boolean ShowSyntheticMembers.x
private static final ShowSyntheticMembers[] ShowSyntheticMembers.$VALUES
public static void ShowSyntheticMembers.main(java.lang.String[])
public static ShowSyntheticMembers[] ShowSyntheticMembers.values()
public static ShowSyntheticMembers ShowSyntheticMembers.valueOf(java.lang.String)
private static void ShowSyntheticMembers.lambda$main$1(java.io.PrintStream,java.lang.Object)
private static java.util.stream.Stream ShowSyntheticMembers.lambda$main$0(java.lang.Class)
static final boolean ShowSyntheticMembers$Inner.$assertionsDisabled
final ShowSyntheticMembers ShowSyntheticMembers$Inner.this$0
protected java.lang.String ShowSyntheticMembers$Inner.clone()
protected java.lang.Object ShowSyntheticMembers$Inner.clone() throws java.lang.CloneNotSupportedException

при компиляции и запуске с JDK 8 дает

private boolean ShowSyntheticMembers.x
private static final ShowSyntheticMembers[] ShowSyntheticMembers.$VALUES
public static void ShowSyntheticMembers.main(java.lang.String[])
public static ShowSyntheticMembers[] ShowSyntheticMembers.values()
public static ShowSyntheticMembers ShowSyntheticMembers.valueOf(java.lang.String)
static boolean ShowSyntheticMembers.access$000(ShowSyntheticMembers)
private static java.util.stream.Stream ShowSyntheticMembers.lambda$main$0(java.lang.Class)
static final boolean ShowSyntheticMembers$Inner.$assertionsDisabled
final ShowSyntheticMembers ShowSyntheticMembers$Inner.this$0
protected java.lang.String ShowSyntheticMembers$Inner.clone()
protected java.lang.Object ShowSyntheticMembers$Inner.clone() throws java.lang.CloneNotSupportedException
  • $VALUES - артефакт сгенерированного компилятором values() реализации.
  • $assertionsDisabled часть реализации оператора assert.
  • this$0 является неявной ссылкой внутреннего класса на его внешний this.
  • Метод clone() с типом возврата Object является методом моста.
  • Метод access$000 помогает получить доступ к полю private внешнего класса из внутренний класс, который необходим до JDK 11.
  • Интересно, что метод syntheti c lambda$main$1, который существует только в скомпилированной версии JDK 11, является частью ссылки на метод System.out::println, но на самом деле здесь не нужно.
    Это побочный эффект o Исправление некоторых проблем, связанных с типом пересечений, следовательно, очень специфично для компилятора c. Изменение .flatMap(…) на .<Object>flatMap(…) в исходном коде приведет к тому, что метод исчезнет даже с этой указанной c версией компилятора.

Итак, так как множество факторов определяют наличие Синтетические c члены, не видимые в исходном коде, не следует искать конкретный метод, используя только тип параметра в качестве критерия.

Если вы хотите получить доступ к public членам, лучше использовать Handler.class.getMethods() вместо Handler.class.getDeclaredMethods(). Или используйте Handler.class.getMethod("handle", Bar.class) для непосредственного получения намеченного метода.

Если вы не хотите жестко кодировать имя метода в виде строки, видимая аннотация во время выполнения может помочь определить правильный метод.

...