【问题标题】:Inner Lambda getting returned in Class.getDeclaredMethods()?在 Class.getDeclaredMethods() 中返回内部 Lambda?
【发布时间】:2020-07-18 06:57:45
【问题描述】:

考虑这个类:

public class Handler
{
    private Supplier<Foo> foo;

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

并考虑这个想要访问handle()方法的反射sn-p。

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 中的新内容(甚至是错误)吗?

【问题讨论】:

  • Java 通过将代码放入自己的私有方法(如果它不直接或间接引用this,则为静态)与绑定变量的参数,然后是接口的参数来对 Lambda 进行解糖有问题。
  • 对于您的问题:使用getMethods()。并且也许也检查返回类型。
  • 不,这对 Java 11 来说并不新鲜。Lambdas 总是以这种方式编译的,因为它们在 Java(即 Java 8)中受支持。在迭代非公共方法时遇到合成方法的可能性从 Java 开始就存在1.1.
  • @Stewart,对,我自己做了一些测试。除了 Holger 的观点之外,当我们使用 lambda 表达式时,它会编译并为 lambda 放置一个私有静态方法,但当我们使用方法引用时,它不会放置。
  • @Stewart 您可以使用special start-up flagthis for example 看到自己正在生成什么

标签: java lambda reflection java-11


【解决方案1】:

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

甚至还有编译器生成的成员,它们是可访问 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 "";
        }
    }
}

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

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

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

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

【讨论】:

    猜你喜欢
    • 2022-12-20
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-10
    • 2022-01-22
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多