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



考虑这个类:

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将方法内部的lambda视为顶级声明方法。这在Java 11中是新的(甚至是一个错误(吗?

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

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

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

但这还不是全部。

以下程序

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

使用JDK8编译和运行时会产生

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的隐式引用
  • 返回类型为Objectclone()方法是一种桥接方法
  • access$000方法有助于从内部类访问外部类的private字段,这在JDK11之前是必需的
  • 有趣的是,只存在于JDK11编译版本中的合成方法lambda$main$1System.out::println方法引用的一部分,但实际上这里不需要
    这是修复某些与交叉点类型相关的问题的副作用,因此非常适合编译器。将源代码中的.flatMap(…)更改为.<Object>flatMap(…)会使该方法消失,即使使用此特定的编译器版本也是如此

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

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

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

最新更新