Java generics: <B extends BaseB> does not match <? extends BaseB>
I have two hierarchies of isomorphic types. The main type of the first is BaseA, and the basic type of the second is BaseB. I know how to convert any object of any subclass of BaseB to the corresponding subtype of BaseA. I want to implement a method that takes an object of type BaseB, defines its class, and builds an object of the corresponding subtype of BaseA. Code example:
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 is <B extends BaseB> not compatible with <? extends BaseB> <? extends BaseB> ? Also, if I try to implement the static conversion method as follows:
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 someone explain to me what I'm doing wrong with Generics?
The problem is that in the transform method, the compiler cannot know that the parameter of type B extends BaseB and the parameter of the second type in the transform class ( ? extends BaseB ), which was obtained from the map, actually represent the same subclass of BaseB . Nothing prevents you from storing an incompatible type on the map:
_map.put(DerB.class, new AnotherDerAtoAnotherDerB()); // the types don't match It is you who guarantee the conformity of types in the map, so you need to tell the compiler, translating it into 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); } When the compiler encounters a variable with a wildcard in its type, it knows that there must be some T that matches what was sent. It does not know what type T is, but it can create a placeholder for this type to refer to the type, which should be T. This placeholder is called capturing this particular template.
I do not know why the compiler cannot understand what capture<? extends BaseB> capture<? extends BaseB> maybe capture<?> extends BaseB , maybe something with type erasure?
Instead, I would execute it as follows:
interface BaseA {} interface BaseB {} class DerA implements BaseA {} class DerB implements BaseB {} interface Transform { BaseA toA(BaseB b); } class DerAtoDerB implements Transform { public BaseA toA(BaseB b) { return new DerA(); } } class Transformations { private static Map<Class<?>, Transform> _map = new HashMap<>(); static { _map.put(DerB.class, new DerAtoDerB()); } public static<B extends BaseB> BaseA transform(B b) { Transform t = _map.get(b.getClass()); return t.toA(b); } } ? means unknown type.
When a variable is of type X , you can assign it a value of type X or any subtype of X , but <<20> "means something else.
This means that there is an unknown type, which can be X or any subtype of X. This is not the same thing.
Example:
public static Transform<? extends BaseA, ? extends BaseB> getSomething(){ // My custom method return new Transform<MySubclassOfA, MySubclassOfB>(); // <-- It does not accept BaseB, only MySubclassOfB } public static BaseA transform(BaseB b){ Transform<? extends BaseA, ? extends BaseB> t = getSomething(); return t.toA(b); // <--- THIS IS WRONG, it cannot accept any BaseB, only MySubclassOfB } In this example, the compiler does not know if t allows any BaseB or what, but I showed an example where it does not work.
This thing compiles:
package com.test; import java.util.HashMap; import java.util.Map; interface BaseA{} interface BaseB{} class DerA implements BaseA{} class DerB implements BaseB{} interface Transform<A,B> { A toA (B b); } class DerAtoDerB implements Transform<BaseA,BaseB> { public DerA toA(DerB b){ return null; } @Override public BaseA toA(BaseB baseB) { return null; } } public class Transformations { private static Map<Class<?>, Transform<? extends BaseA, ? super BaseB>> _map = new HashMap<Class<?>, Transform<? extends BaseA, ? super BaseB>>(); static { _map.put(DerB.class, new DerAtoDerB()); } public static <B extends BaseB> BaseA transform(B b){ Transform<? extends BaseA, ? super BaseB> t = _map.get(b.getClass()); return t.toA(b); } } The changes made to your code are as follows:
DerAtoDerBnow implementsTransform<BaseA,BaseB>instead ofTransform<DerA,DerB>- The type of the second general
Mapparameter has changed toTransform<? extends BaseA, ? super BaseB>Transform<? extends BaseA, ? super BaseB>Transform<? extends BaseA, ? super BaseB>- note the use ofsuperinstead ofextends- this is the opposite type boundary.
The basic concept of Java generics: if ChildClass extends ParentClass, this does NOT mean that YourApi <ChildClass> extends YourApi <ParentClass>. For instance:.
NumberTransform<String, ? extends Number> intTransform = new IntegerTransform<String, Integer>(); // work with Integer numbers only NumberTransform<String, ? extends Number> longTransform = new LongTransform<String, Long>(); // work with Long numbers only longTransform.toA((Integer) 1); // you are trying to make this and got compilation error. To help the compiler replace your t initialization:
Transform<? extends BaseA, B> t = (Transform<? extends BaseA, B>) _map.get(b.getClass());