4

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.

  • See https://stackoverflow.com/a/18941285 – Gustavo Passini Apr 07 '19 at 15:40
  • Due to type erasure (the type parameters are *removed* by the compiler); the following is fine: `Class> clazz = (Class)Set.class;` – Daniele Apr 07 '19 at 15:52
  • @Daniele, thank you for solution. It works, but it produces compiler warning. I am trying to find solution without warnings. Will make some clarification to my question. – Sergei Tarasov Apr 07 '19 at 16:07
  • Well you can safely add `@SuppressWarnings({"unchecked", "rawtypes"})` . Maybe have a look at this SO question: https://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java – Daniele Apr 07 '19 at 16:11
  • @Daniele, this initialization will be in clients code of my API. I am trying to find solution that will not polutte their code with ```@SuppressWarnings``` – Sergei Tarasov Apr 07 '19 at 16:19
  • It is not directly possible, and would be of very limited use anyhow: A `Class>` is basically "not distinguishable" from a `Class>` anyhow. How will the clients of your API use this part of the code? Maybe there is a cleaner solution. (Something like Guava TypeToken https://github.com/google/guava/wiki/ReflectionExplained could be relevant here (but I hesitate to mention my [types library](https://github.com/javagl/Types), because it's built around the `Type` class, which is a generalization of the `Class` class...)) – Marco13 Apr 07 '19 at 16:37
  • 1
    @SergeiTarasov : I do not see an elegant way around suppressing uncheck types here. If the initialization of the set is done by the clients of your API, perhaps you should add an additional implementation on your end which will do the suppressing of the type casting on your end, instead of the client end..... – Rann Lifshitz Apr 07 '19 at 17:26

1 Answers1

3

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.

newacct
  • 119,665
  • 29
  • 163
  • 224