How to keep generic type of nested generics with c

2019-09-14 10:43发布

问题:

The standard way in Java to work around type erasure is to pass a class token into the constructor. For example we could define a generic property class like this:

class Prop<T> {
    public Prop(Class<T> type) {
        this.type = type;
    }
    Class<T> type;
    T t;
}

class IntProp extends Prop<Integer> {
    public IntProp() {
        super(Integer.class);
    }
}

But what if I now want to use another generic type argument, such as a list and also keep its generics type. I would have liked to do this:

class ListProp<J> extends Prop<ArrayList<J>> {
    Class<J> subtype;
    public ListProp(Class<J> type) {
        super(ArrayList<J>.class);
        subtype = type;
    }
}

class IntListProp extends ListProp<Integer> {
    public IntListProp() {
        super(Integer.class);
    }
}

But of course super(ArrayList<J>.class) does not compile, neither does super(ArrayList.class) . What's the best way to solve this?

回答1:

The generics kung fu you need to make your ListProp class compile is this line:

super((Class<List<T>>)(Class<?>)List.class); // compiles

Attempting to cast directly from List.class to Class<List<T>>:

super((Class<List<T>>)List.class); //compile error

results in a compile error:

Inconvertible types; cannot cast 'java.lang.Class' to 'java.lang.Class>

But if you first cast to typed class Class<?>, albeit an unknown type, you can then cast it the desired typed class.

The full compilable ListProp class is then.

class ListProp<T> extends Prop<List<T>> {
    Class<T> subtype;
    public ListProp(Class<T> type) {
        super((Class<List<T>>)(Class<?>)List.class); // magic double cast
        subtype = type;
    }
}

Something else you may consider, in case you need special code for creating/returning a list, is a typed getter for t on Prop:

public T getT() {
    return t;
}

which you can then covariantly override in ListProp to return a List<T>

@Override
public List<T> getT() {
    return Arrays.asList(subtype.newInstance()); // or whatever
}

It should be noted that you only need the class token if your implementation uses the class, which is not shown in your example code. If you don't actually use the class token, you can let type inference do the typing for you.



回答2:

Small Intro

I know one way you can workaround the problem. Type erasure works to erase types after when compiling since they are not needed to run. This is probably why you can't just access the class List with type. List in itself uses generics and so it would not make sense, or possible, to provide a class for that since in your case T is actually the type of the list elements. The class is actually ArrayList. What you are looking for is the type of collection you are using and the type of element you are using. I've changed your code a bit so that you can accept a constructor for both types.


  1. T is now the type of the elements in the collection
  2. J is now the Collection type

Code

  class ListProp<T, J> extends Prop<T, J> {
            Class<T> subtype;
            Class<J> subtypeElementList:

            public ListProp(Class<T> typeElement, Class<J> typeList) {
                super(typeElement, typeList);
                subtype = typeElement;
                subtypeElementList = typeList;
            }

        }

        class IntListProp extends ListProp<Integer, ArrayList> {
            public IntListProp() {
                super(Integer.class, ArrayList.class);
            }
        }

        class Prop<T, J> {
            // TODO: Maybe here the elements again? Depends on what you want to do...
            //
            // or maybe just use the integer as you had previously.
            public Prop(Class<T> integerClass, Class<J> arrayListClass) {

            }
        }