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?

+5
source share
5 answers

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); } 
+2
source

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); } } 
0
source

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

0
source

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:

  • DerAtoDerB now implements Transform<BaseA,BaseB> instead of Transform<DerA,DerB>
  • The type of the second general Map parameter has changed to Transform<? extends BaseA, ? super BaseB> Transform<? extends BaseA, ? super BaseB> Transform<? extends BaseA, ? super BaseB> - note the use of super instead of extends - this is the opposite type boundary.
0
source

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()); 
-2
source

Source: https://habr.com/ru/post/1204316/


All Articles