Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - issue initializing class with type parameters

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.

like image 758
thisdotnull Avatar asked Mar 13 '16 17:03

thisdotnull


People also ask

Can a generic class be instantiated without specifying an actual type argument?

You can create an instance of a generic class without specifying the actual type argument. An object created in this manner is said to be of a raw type. The Object type is used for unspecified types in raw types.

How do you initialize a generic type in Java?

If you want to initialize Generic object, you need to pass Class<T> object to Java which helps Java to create generic object at runtime by using Java Reflection.

How to use generic type as parameter in Java?

Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.

How do you declare a generic type in a class?

To update the Box class to use generics, you create a generic type declaration by changing the code "public class Box" to "public class Box<T>". This introduces the type variable, T, that can be used anywhere inside the class.


2 Answers

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

like image 194
Has QUIT--Anony-Mousse Avatar answered Nov 04 '22 09:11

Has QUIT--Anony-Mousse


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)

like image 37
nyname00 Avatar answered Nov 04 '22 09:11

nyname00