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

Ste*_*art 5 java reflection lambda java-11

考虑这个类:

public class Handler
{
    private Supplier<Foo> foo;

    public void handle( Bar bar )
    {
        foo = () -> bar.getFoo();
    }
}
Run Code Online (Sandbox Code Playgroud)

并考虑这个想要访问 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
    }
}
Run Code Online (Sandbox Code Playgroud)

而不是寻找

  • 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"
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下这里发生了什么吗?

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

Hol*_*ger 3

您必须小心有关已编译类成员的假设。

\n\n

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

\n\n

除此之外,还可以有不同的合成成员。从 Java\xc2\xa01.1 到 Java\xc2\xa010,嵌套类可以通过合成帮助器方法访问彼此的私有成员(该方法在 Java\xc2\xa011 中已过时)。此外,重写泛型类的方法或使用协变返回类型可能会导致生成合成桥方法。

\n\n

而\xe2\x80\x99s还不是全部。

\n\n

下面的程序

\n\n
import java.util.Arrays;\nimport java.util.stream.Stream;\n\npublic enum ShowSyntheticMembers {\n    ;\n    public static void main(String[] args) {\n        Stream.of(ShowSyntheticMembers.class, Inner.class)\n            .flatMap(cl -> Stream.concat(Arrays.stream(cl.getDeclaredFields()),\n                                         Arrays.stream(cl.getDeclaredMethods())))\n            .forEach(System.out::println);\n    }\n    private boolean x;\n    class Inner {\n        protected String clone() {\n            assert x;\n            return "";\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用 JDK\xc2\xa011 编译时打印:

\n\n
import java.util.Arrays;\nimport java.util.stream.Stream;\n\npublic enum ShowSyntheticMembers {\n    ;\n    public static void main(String[] args) {\n        Stream.of(ShowSyntheticMembers.class, Inner.class)\n            .flatMap(cl -> Stream.concat(Arrays.stream(cl.getDeclaredFields()),\n                                         Arrays.stream(cl.getDeclaredMethods())))\n            .forEach(System.out::println);\n    }\n    private boolean x;\n    class Inner {\n        protected String clone() {\n            assert x;\n            return "";\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用 JDK\xc2\xa08 编译并运行时会产生

\n\n
private boolean ShowSyntheticMembers.x\nprivate static final ShowSyntheticMembers[] ShowSyntheticMembers.$VALUES\npublic static void ShowSyntheticMembers.main(java.lang.String[])\npublic static ShowSyntheticMembers[] ShowSyntheticMembers.values()\npublic static ShowSyntheticMembers ShowSyntheticMembers.valueOf(java.lang.String)\nprivate static void ShowSyntheticMembers.lambda$main$1(java.io.PrintStream,java.lang.Object)\nprivate static java.util.stream.Stream ShowSyntheticMembers.lambda$main$0(java.lang.Class)\nstatic final boolean ShowSyntheticMembers$Inner.$assertionsDisabled\nfinal ShowSyntheticMembers ShowSyntheticMembers$Inner.this$0\nprotected java.lang.String ShowSyntheticMembers$Inner.clone()\nprotected java.lang.Object ShowSyntheticMembers$Inner.clone() throws java.lang.CloneNotSupportedException\n
Run Code Online (Sandbox Code Playgroud)\n\n
    \n
  • $VALUES是编译器生成的实现的工件values()
  • \n
  • $assertionsDisabled声明执行的一部分assert
  • \n
  • this$0是内部类\xe2\x80\x99 对其外部的隐式引用this
  • \n
  • clone()返回类型为 的方法是Object一个桥接方法。
  • \n
  • access$000方法有助于private从内部类访问外部类的字段,这是JDK\xc2\xa011之前需要的。
  • \n
  • 有趣的是,合成方法lambda$main$1仅存在于JDK\xc2\xa011编译版本中,它是System.out::println方法引用的一部分,但实际上这里不需要。
    \n它\xe2\x80\x99是修复某些交叉类型相关问题的副作用,因此,非常特定于编译器。即使使用这个特定的编译器版本,在源代码中更改.flatMap(\xe2\x80\xa6).<Object>flatMap(\xe2\x80\xa6)也会使该方法消失。
  • \n
\n\n
\n\n

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

\n\n

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

\n\n

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

\n