英文:
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<Foo> foo;
public void handle( Bar bar )
{
foo = () -> bar.getFoo();
}
}
And consider this reflection snippet which wants to access the handle() method.
for( Method method : Handler.class.getDeclaredMethods() )
{
if ( method.getParameterCount() == 1 && 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 "private static"
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()
实现的结果。$assertionsDisabled
是assert
语句实现的一部分。this$0
是内部类对外部this
的隐式引用。- 具有返回类型为
Object
的clone()
方法是一个桥接方法。 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 -> 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 "";
}
}
}
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 generatedvalues()
implementation.$assertionsDisabled
part of the implementation of theassert
statement.this$0
is the inner class’ implicit reference to its outerthis
.- The
clone()
method with the return typeObject
, is a bridge method. - The
access$000
method helps to access theprivate
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 theSystem.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.<Object>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("handle", 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论