Sergei Tarasov
Sergei Tarasov

Reputation: 53

How to initialize a variable of type java.lang.Class to Collection with generics without compiler warnings?

I need to initialize a variable of type Class<Set<String>>

When I use Set.class it returns variable of Class<Set>

Class<Set> clazz = Set.class;

When I try

Class<Set<String>> clazz = Set<String>.class;

I have a compile error.

Upvotes: 4

Views: 723

Answers (1)

newacct
newacct

Reputation: 122449

First, you need to understand that, at runtime, there is only one Class object representing the Set interface. There are no separate Class objects for Set<String>, Set<Integer>, etc. So the most that your variable of type Class<Set<String>> can do at runtime is point to this one Set class object, and the question is does it make sense for it to do so?

Let's consider what you can do with a variable of type Class<Set<String>>.

  • You could call its .cast() method. Normally, if you have a Class<T> clazz, clazz.cast(x) returns type T, and what it does at runtime is it checks that the passed object is an instance of the class, and if not, it throws an exception; that's why it's safe for it to return the type T. However, if your variable points to the Class object that represents the Set interface, then its .cast() can only check that the object is an instance of Set, not that it is an instance of Set<String> (which is not possible anyway, since objects do not know their generic type arguments at runtime; if you did a cast (Set<String>)x, it would give an unchecked cast warning; so it would not make sense if you were able to "bypass" the warning by doing Set<String>.class.cast(x) without a warning), so it cannot "safely" return type Set<String>.
  • You could call its .isInstance() method. Normally, if you have a Class<T> clazz, clazz.isInstance(x) returns true or false depending on whether the passed object is an instance of T. But just with .cast(), it is not possible to check the generic type argument of an object at runtime, so calling a Class<Set<String>>'s .isInstance() method might not give the "correct" answer.
  • You could create a new instance by calling the .newInstance() method or by getting a constructor and using the constructor's .newInstance() method. Well, in this case, Set is an interface and cannot be instantiated; let's instead consider say, HashSet. With a Class<HashSet<String>>, you can call .newInstance() to get a HashSet<String> using the no-parameter constructor. In this case, it is safe, because there is no difference between new HashSet() and new HashSet<String>() and new HashSet<Integer>() at runtime. However, if you get a constructor with parameters, it may be potentially unsafe if the type T is used in the parameters, because there is no way for it to check that the passed arguments match those types (since it doesn't know T at runtime).

So as you can see, a Class<Set<String>> cannot safely fulfill the contract of the Class class for some of its functionality. Therefore, you should not be able to get a Class<Set<String>> without warnings. If you are sure that it is safe for your use case to consider the Class object representing Set as a Class<Set<String>>, then you can manually force it by doing (Class<Set<?>>)(Class<?>)Set.class, but you will get a warning which means you take responsibility for making sure it's safe.

Upvotes: 3

Related Questions