Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics: <B extends BaseB> does not match <? extends BaseB>

Tags:

java

generics

I have two isomorphic type hierarchies. The base type of the first one is BaseA and the base type of the second one is BaseB. I know how to transform any object of any subclass of BaseB to its corresponding subtype of BaseA. I want to implement a method which takes object of type BaseB determines its class and constructs an object of the corresponding subtype of BaseA. Example code:

public interface BaseA...
public interface BaseB...
public class DerA implements BaseA...
public class DerB implements BaseB...
...
public interface Transform<A,B> {
    A toA (B b);
}

public class DerAtoDerB implements Transform<DerA,DerB> {
    DerA toA (DerB b){...}
}

public class Transformations {
    private static Map<Class<?>, Transform<? extends BaseA, ? extends BaseB>> _map = 
        new HashMap<>();
static {
    _map.put(DerB.class, new DerAtoDerB());
    }

public static <B extends BaseB> BaseA transform(B b){
    Transform<? extends BaseA, ? extends BaseB> t = _map.get(b.getClass());
    return t.toA(b); // Compile error: Transform<A,B#2> cannot be applied to given types
}

Why <B extends BaseB> is not compatible with <? extends BaseB> ? Also if I try implementing the static transform method like this:

public static BaseA transform(BaseB b){
    Transform<? extends BaseA, ? extends BaseB> t = _map.get(b.getClass());
    return t.toA(b); // Compile error: Transform<A,B> cannot be applied to given types
}

I get a compilation error: Transform<A,B> cannot be applied to given types

Can anyone explain me what I am doing wrong with Generics?

like image 280
egelev Avatar asked Oct 09 '14 10:10

egelev


1 Answers

The problem is that in the transform method the compiler can't know that the type parameter B extends BaseB and the second type parameter in the Transform class (? extends BaseB) that was gotten from the map actually represent the same subclass of BaseB. Nothing stops you from storing an incompatible type in the map:

_map.put(DerB.class, new AnotherDerAtoAnotherDerB()); // the types don't match

You are the one who guarantees that the types in the map match, so you need to tell the compiler by casting it to the correct type:

@SuppressWarnings("unchecked")
public static <B extends BaseB> BaseA transform(B b) {
  Transform<? extends BaseA, B> t = 
    (Transform<? extends BaseA, B>)_map.get(b.getClass());
  return t.toA(b);
}
like image 155
Jordão Avatar answered Nov 14 '22 23:11

Jordão