6

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?

Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
egelev
  • 1,175
  • 2
  • 11
  • 27
  • First answer may be useful for you http://stackoverflow.com/questions/16449799/how-do-generics-of-generics-work – aalku Oct 09 '14 at 11:31

5 Answers5

3

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);
}
Jordão
  • 55,340
  • 13
  • 112
  • 144
0

When the compiler encounters a variable with a wildcard in its type it knows that there must have been some T that matches what was sent in. It does not know what type T represents, but it can create a placeholder for that type to refer to the type that T must be. That placeholder is called the capture of that particular wildcard.

I don't know why the compiler can't figure out that capture<? extends BaseB> could be capture<?> extends BaseB, maybe something with type erasure?

I would instead implement it like this:

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);
    }
}
Alexander Kjäll
  • 4,246
  • 3
  • 33
  • 57
0

? 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 "? extends X" means something else.

It means there is an unknown type that may be X or any subtype of X. It 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 the example the compiler does not know if t admits any BaseB or what but I shown an example where it doesn't.

aalku
  • 2,860
  • 2
  • 23
  • 44
0

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 I made to your code are the following:

  1. DerAtoDerB now implements Transform<BaseA,BaseB>, instead of Transform<DerA,DerB>
  2. Type of second generic parameter of Map has changed to Transform<? extends BaseA, ? super BaseB> - pay attention to use of super instead of extends - it's the opposite type bound.
Haspemulator
  • 11,050
  • 9
  • 49
  • 76
-2

Main concept of Java generics: if ChildClass extends ParentClass it DOES NOT mean YourApi<ChildClass> extends YourApi<ParentClass>. E.g.:

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 compiler replace your t initialization:

Transform<? extends BaseA, B> t = (Transform<? extends BaseA, B>) _map.get(b.getClass());
ursa
  • 4,404
  • 1
  • 24
  • 38
  • How does this answer the question? This should rather be a comment. – Konstantin Yovkov Oct 09 '14 at 10:58
  • Actually I am not trying to do this, because the concrete implementation of Transform is determined by the class of the argument,i.e. I would not pass Integer to something which expects Long. – egelev Oct 09 '14 at 11:00
  • you write "Transform extends BaseA, ? extends BaseB> t = ...". part "? extends BaseB" means it can be ANY subtype of BaseB. It can be DerB, FooB, LambdaB, etc... then you pass concrete "B b" into transformer, which from compiler's point of view can be any of Transformer<..., DerB>, Transformer<..., FooB>, etc – ursa Oct 09 '14 at 11:16
  • ursa is right. @egelev the compiler can't know what you are trying to do, for it you might be doing what ursa say and that error is protecting you from it. – aalku Oct 09 '14 at 11:26
  • I tried to explain it other way but it is the same concept. – aalku Oct 09 '14 at 11:27