Given this:
Set<?> set1 = new HashSet<Great>();
That works if you visualize what the unbounded wildcard mean, the slotted type on unbounded wildcard is compared against the extends
, so if you do that explicitly.
Set<? extends Object> set1 = new HashSet<Great>();
To read, is Great extending Object? Yes, so that compiles.
Then given this:
Set<Class<Great>> set3 = new HashSet<Class<Great>>();
As why it does work, if you extract the parameter of Set and HashSet, they are Class<Great>
, those two Class<Great>
are exactly the same type.
In absence of wildcard, types are compared directly, in verbatim.
And if we will write the set3 so it accepts covariant type(this compiles):
Set<? extends Class<Great>> set3a = new HashSet<Class<Great>>();
To read, is HashSet's Class<Great>
type-compatible or covariant against Set's Class<Great>
? Yes it is. Hence it compiles.
Though of course no one would write that kind of variable declaration if they are just exactly the same type, it's redundant. The wildcard is being used by the compiler to determine if the generic's concrete class or interface parameter on the right side of assignment is compatible to left side generic's concrete/interface(ideally interface, like the following).
List<? extends Set<Great>> b = new ArrayList<HashSet<Great>>();
To read it, is HashSet<Great>
covariant to Set<Great>
? yes it is. Hence it compiles
So let's get back at your code scenario:
Set<Class<?>> set3 = new HashSet<Class<Object>>();
In this case, the same rule applies, you read it starting from innermost, is Object compatible to wildcard? Yes. Then after that you go to next outermost type, which happens not to have a wildcard. So in absence of wildcard, the compiler will do a verbatim check between Class<Object>
and Class<?>
, which are not equal, hence a compile error.
If it has wildcard on outermost, that will compile. So what you probably meant is this, this compiles:
Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();
Let's make a more illuminating example though, let's use interface (Class is concrete type), say Set. This compiles:
List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();
So read it from inside out to find out why that code compiles, and do it explicitly:
Innermost: Is Object
compatible to ? Extends Object
? Sure it is.
Outermost: Is HashSet<Object>
compatible to ? extends Set<? extends Object>
? sure it is.
On number 1, it is this(compiles):
Set<? extends Object> hmm = new HashSet<Object>();
On number 2, it is this(compiles):
List<? extends Set<? extends Object>> b = new ArrayList<HashSet<Object>>();
Now let's try to remove the outermost wildcard, there would be no type-compatible/covariant checks will be done by the compiler, things will be compared in verbatim now.
So you know now the answer to the following, shall these compile?
List<Set<?>> b = new ArrayList<HashSet<Object>>();
// this is same as above:
List<Set<? extends Object>> b = new ArrayList<HashSet<Object>>();
So you guess it already, correct... that will not compile :-)
To correct the above, do either of these:
List<? extends Set<? extends Object>> b = new ArrayList<HashSet<Object>>();
List<? extends Set<?>> b = new ArrayList<HashSet<Object>>();
Then, to correct your code, do either of these:
Set<? extends Class<? extends Object>> singletonSet =
new HashSet<Class<Object>>();
Set<? extends Class<?>> singletonSet = new HashSet<Class<Object>>();