Issue
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 ?
Solution
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:
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
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.
Answered By - Holger
Answer Checked By - David Goodson (JavaFixing Volunteer)