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.
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.
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.
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.
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.
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
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With