Reflection - Method::getGenericReturnType no generic - visbility

Let's take it slow here. First of all, here is why a bridge method is generated to begin with. Even if you drop generics, there will still be a bridge method. That is, this code:

static class A {
    public String foo() { return null; }
}

public static class B extends A {}

will still generate a foo method with ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC. You can read the bug description and understand why that is needed.

On the other hand if you make A public, such a method will not be generated, the reason should be obvious, considering the previous bug explanation (I hope). So the idea is that if you have a non-public class, javac will generate a bridge method for the scenario above.


Now, if you add generics into that mix of synthetic methods, things start to shed some light. For example, you have this:

interface WithGeneric<T> {
    public WithGeneric<T> self(T s);
}

public class Impl implements WithGeneric<String> {

    @Override
    public WithGeneric<String> self(String s) {
        return null;
    }
}

There will be a bridge method generated too in Impl.class, but its declaration is going to be the erasure of the interface. In other words there will be two methods in Impl.class:

public WithGeneric<String> self(String) {...}

public WithGeneric self(Object) {...}

If you glue these two things:

  • in case of non-public classes a bridge method will be created (so that reflection would work)

  • in case of generics an erased bridge method will be created (so that erased calls would work)

things will make (somehow) sense.


When you declare A public, B.class.getMethods()[0] is not referencing B; It is referencing A.foo(), where the method is declared and the type is obtained because of the existance of the signature.

enter image description here


Declaring A non-public forces B.class.getMethods()[0] reference B.foo().

As there is no declaration of the inherited method, the type can't be obtained from the call to getGenericReturnType due to type erasure being applied to generics.

In compile time:

List<String> foo() becomes List foo().

And that's all the information B could give to you regarding the method's return type, since the signature in B.foo() was just removed.

enter image description here


Both A and B hold the same return type for foo() : java.util.List (without parameter type)

This is A.foo()'s declared return type. The same than B.foo():

enter image description here

The difference is that A has a valid signature, so it will complete the result from the call to getGenericReturnType() by adding the parameter types.

In the case of B, it will only show what it knows: just the return type.


I suffered strong headaches trying to solve the visibility puzzle. For a complete explanation of why this happens, look up at Eugene's answer. Seriously, you can learn a lot from this type of guy.