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

huangapple go评论65阅读模式
英文:

Inner Lambda getting returned in Class.getDeclaredMethods()?

问题

考虑以下这个类:

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] ) )
    {
        // 这就是你要找的方法
    }
}

不过与其找到:

  • 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 中的新特性(还是一个 Bug)吗?

英文:

Consider this class:

public class Handler
{
    private Supplier&lt;Foo&gt; foo;

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

And consider this reflection snippet which wants to access the handle() method.

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

Instead of finding

  • public void Handler.handle(Bar)

It finds

  • private Foo Handler.lambda$3(Bar)

Which obviously then throws the Exception:

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

Can someone explain what is going on here, please?

It looks like Java considers the lambda inside the method as a top-level declared method. Is this new (or even a bug) in Java 11 ?

答案1

得分: 3

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

甚至有一些由编译器生成的成员是可访问 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() 实现的结果。
  • $assertionsDisabledassert 语句实现的一部分。
  • this$0 是内部类对外部 this 的隐式引用。
  • 具有返回类型为 Objectclone() 方法是一个桥接方法。
  • access$000 方法帮助从内部类访问外部类的 private 字段,这在 JDK 11 之前是必需的。
  • 有趣的是,合成方法 lambda$main$1 仅存在于 JDK 11 编译版本中,是 System.out::println 方法引用的一部分,但实际上在这里不需要。这是对特定编译器的某些交叉类型相关问题的修复的副作用,因此非常与编译器相关。在源代码中将 .flatMap(…) 更改为 .<Object>flatMap(…) 即可使该方法在此特定编译器版本中消失。

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

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

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

英文:

You have to be careful with assumptions about the members of the compiled class.

There are even compiler-generated members which are part of the accessible API, like the default constructor or the values() and valueOf(String) methods of enum types. Further, compiled constructors of inner classes and enum types may have more parameters than visible in the source code and due to type erasure, the signatures in the compiled methods may differ from the source code.

Besides that, there can be different synthetic members. From Java 1.1 to Java 10, nested classes may access each others private members via synthetic helper methods (which became obsolete with Java 11). Also, overriding methods of generic classes or using covariant return types may cause the generation of a synthetic bridge method.

And that’s still not all.

The following program

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 -&gt; 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 &quot;&quot;;
        }
    }
}

prints when compiled with JDK 11:

<!-- language: lang-none -->

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

while compiling and running with JDK 8 yields

<!-- language: lang-none -->

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 is an artifact of the compiler generated values() implementation.
  • $assertionsDisabled part of the implementation of the assert statement.
  • this$0 is the inner class’ implicit reference to its outer this.
  • The clone() method with the return type Object, is a bridge method.
  • The access$000 method helps to access the private field of the outer class from the inner class, which is needed prior to JDK 11.
  • Interestingly, the synthetic method lambda$main$1, which only exists in the JDK 11 compiled version is part of the System.out::println method reference, but actually not needed here.
    It’s a side effect of a fix for certain intersection type related issues, hence, very compiler-specific. Changing .flatMap(…) to .&lt;Object&gt;flatMap(…) in the source code would make the method disappear even with this specific compiler version.

So, since a lot of factors determine the presence of synthetic members not visible in the source code, you should not search for a particular method by only using the parameter type as criteria.

When you want to access the public members, you better use Handler.class.getMethods() instead of Handler.class.getDeclaredMethods(). Or use Handler.class.getMethod(&quot;handle&quot;, Bar.class) to directly get the intended method.

If you don’t want to hardcode the method name as a string, a runtime visible annotation could help to identify the right method.

huangapple
  • 本文由 发表于 2020年4月6日 17:51:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/61057070.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定