Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inner Lambda getting returned in Class.getDeclaredMethods()?

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 ?

like image 515
Stewart Avatar asked Oct 16 '22 04:10

Stewart


1 Answers

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

like image 54
Holger Avatar answered Nov 15 '22 07:11

Holger