Java - issue initializing class with type parameters
Asked Answered
G

2

6

I am having an issue initializing a class with type parameter. It seems to be a shortcoming of Java's type inference and I would like to know if there's a way around this or a better way of achieving this.

public class ParentModel {}

public class ChildModel extends ParentModel {}

public class Service<E extends ParentModel, T extends Collection<E>> {
    private Class<T> classOfT;
    private Class<E> classOfE;

    public Service(Class<E> classOfE, Class<T> classOfT) {
        this.classOfE = classOfE;
        this.classOfT = classOfT;
    }
}

public class BusinessLogic {
    public void someLogic() {
        Service<ChildModel, ArrayList<ChildModel>> service = new 
            Service<ChildModel, ArrayList<ChildModel>>(ChildModel.class, ArrayList.class);
    }
}

Compile-time error is in BusinessLogic::someLogic():

The constructor Service<ChildModel, ArrayList<ChildModel>>(Class<ChildModel>, Class<ArrayList>) is undefined

Compiled to Java 7.

Gook answered 13/3, 2016 at 17:40 Comment(1)
Ate you intending to have Class fields or fields of whatever type T and E are?Troll
C
2

Because generics in Java are implemented "by erasure", there is no Class<ArrayList<ChildModel>>>, only a Class<ArrayList>.

What you can do is to allow supertypes.

Class<? super T> classOfT;
Class<? super E> classOfE;
public Service(Class<? super E> classOfE, Class<? super T> classOfT) {

alternatively, you can double-cast the class:

Class<ArrayList<Integer>> clazz =
    (Class<ArrayList<Integer>>) (Class<? super ArrayList>) 
    ArrayList.class;

But beware: the class is just ArrayList - Java will not perform additional type checks at runtime on the generics. See for yourself:

ArrayList<Integer> a1 = new ArrayList<>();
ArrayList<Double> a2 = new ArrayList<>();
System.out.println(a1.getClass() == a2.getClass());

It is the same class. At runtime, the generics are virtually gone

Cerf answered 14/3, 2016 at 1:31 Comment(2)
this Class<ArrayList<Something>> clazz = (Class<ArrayList<Something>>) ArrayList.class; should not compile, does it? I like the idea of using super thoughRosarosabel
Sorry, you need a double-cast, with either super or extends.Cerf
R
1

Since there is no such thing as ArrayList<ChildModel>.class there will be no elegant way to solve this. You can pass a raw type to you your constructor, as mentioned by Yassin, like this:

Service<ChildModel, ArrayList<ChildModel>> s1 = 
        new Service<>(ChildModel.class, (Class) ArrayList.class) 

The difference to your invocation is that here we're using the raw type Class, while in in your example the type Class<ArrayList> is used (so this is no bug).

Another option would be to get the type from an actual instance:

Class<ArrayList<ChildModel>> fromObj = 
        (Class<ArrayList<ChildModel>>) new ArrayList<ChildModel>(0).getClass();

This is more verbose, but I would prefer that over the raw type (in both cases you will get compiler warnings)

Rosarosabel answered 13/3, 2016 at 22:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.