Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Java claim there's 2 declared methods when bounded generics are involved?

With the following definitions:

public interface BaseService<T, ID> {

    T findOne(ID id);

}

public class BaseServiceImpl<T,ID extends Serializable> implements BaseService<T, ID> {

    @Override
    public T findOne(ID id) {
        return null;
    }

}

Why does BaseServiceImpl.class.getDeclaredMethods() return 2 methods:

  • public java.lang.Object BaseServiceImpl.findOne(java.io.Serializable)
  • public java.lang.Object BaseServiceImpl.findOne(java.lang.Object)

Is there a way to filter these out?

like image 779
kaqqao Avatar asked May 22 '17 11:05

kaqqao


1 Answers

This is a consequence of type erasure. On the byte code level, generic signatures are only an additional attribute of the methods, not used for the JVM’s method dispatch. The actual byte code level signature is derived from the first type of the type variable’s bound, e.g. for a type variable T extends Number&Serializable, the raw signature’s substitute for T would be Number.

For your declaration,

public interface BaseService<T, ID> {
    T findOne(ID id);
}

T and ID are substituted with Object; the method’s erased signature is Object findOne(Object).

For the subtype declaration

public class BaseServiceImpl<T,ID extends Serializable> implements BaseService<T, ID> {
    @Override
    public T findOne(ID id) {
        return null;
    }
}

the erased type of ID extends Serializable is Serializable, which causes the implementation method to have the erased signature Object findOne(Serializable).

To ensure that code using the interface BaseService, calling the method Object findOne(Object), will find the implementation method, the compiler generates a bridge method having the signature Object findOne(Object) and consisting of a plain delegation to Object findOne(Serializable), performing type casts where necessary.

You can identify the bridge method by calling isBridge() on the Method instance.

You can also use the knowledge of how the type erasure works to influence the result. By changing the declaration to

public class BaseServiceImpl<T, ID extends Object&Serializable>
      implements BaseService<T, ID> {
    @Override
    public T findOne(ID id) {
        return null;
    }
}

there is no semantic difference regarding the generic type system, but the erasure of ID extends Object&Serializable will be Object, hence, the resulting erasure of the findOne method will be identical to the erasure of the interface method, therefore, no bridge method will be needed in this case.

like image 73
Holger Avatar answered Oct 03 '22 15:10

Holger