内部 Lambda 在 Class.getDeclaredMethods() 中返回?

Inner Lambda getting returned in Class.getDeclaredMethods()?

考虑这个 class:

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
    }
}

而不是查找

找到

这显然会抛出异常:

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

有人能解释一下这是怎么回事吗?

看起来 Java 将方法内部的 lambda 视为顶级声明方法。这是 Java 11 中的新内容(甚至是错误)吗?

您必须小心对已编译 class 的成员的假设。

甚至还有编译器生成的成员,它们是可访问 API 的一部分,例如默认构造函数或 enum 类型的 values()valueOf(String) 方法。此外,内部 classes 和枚举类型的编译构造函数可能比源代码中可见的参数更多,并且由于 type erasure,编译方法中的签名可能与源代码。

除此之外,还可以有不同的合成成员。从 Java 1.1 到 Java 10,嵌套的 classes 可以通过合成辅助方法(在 Java 11 中已过时)访问彼此的私有成员。此外,覆盖泛型 classes 的方法或使用协变 return 类型可能会导致生成合成桥接方法。

这还不是全部。

下面的程序

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 "";
        }
    }
}

使用 JDK11:

编译时打印
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(java.io.PrintStream,java.lang.Object)
private static java.util.stream.Stream ShowSyntheticMembers.lambda$main[=11=](java.lang.Class)
static final boolean ShowSyntheticMembers$Inner.$assertionsDisabled
final ShowSyntheticMembers ShowSyntheticMembers$Inner.this[=11=]
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[=12=]0(ShowSyntheticMembers)
private static java.util.stream.Stream ShowSyntheticMembers.lambda$main[=12=](java.lang.Class)
static final boolean ShowSyntheticMembers$Inner.$assertionsDisabled
final ShowSyntheticMembers ShowSyntheticMembers$Inner.this[=12=]
protected java.lang.String ShowSyntheticMembers$Inner.clone()
protected java.lang.Object ShowSyntheticMembers$Inner.clone() throws java.lang.CloneNotSupportedException
  • $VALUES 是编译器生成的 values() 实现的产物。
  • $assertionsDisabled部分执行assert语句。
  • this[=20=] 是内部 class 对其外部 this.
  • 的隐式引用
  • return 类型 Objectclone() 方法是一种桥接方法。
  • access[=24=]0 方法有助于从内部 class 访问外部 class 的 private 字段,这是在 JDK 之前需要的 11 .
  • 有意思的是,JDK11编译版本才存在的合成方法lambda$mainSystem.out::println方法引用的一部分,但实际上这里不需要。
    这是修复某些与交集类型相关的问题的副作用,因此非常特定于编译器。在源代码中将 .flatMap(…) 更改为 .<Object>flatMap(…) 将使该方法消失,即使使用此特定编译器版本也是如此。

因此,由于很多因素决定了在源代码中不可见的合成成员的存在,因此您不应该仅使用参数类型作为标准来搜索特定方法。

当您想访问public 成员时,最好使用Handler.class.getMethods() 而不是Handler.class.getDeclaredMethods()。或使用 Handler.class.getMethod("handle", Bar.class) 直接获取预期的方法。

如果您不想将方法名称硬编码为字符串,运行时可见注释可以帮助识别正确的方法。