7

Sometimes I just don't get generics. I often use the most generic version of the collections in the code. For instance if I need a set of just anything I would write something like:

Set<?> set1 = new HashSet<Object>();

It is allowed by the compiler and why shouldn't it - Set<?> is a as general as Set<Object> (or even more generic..). However if I use "generics of generics" making it "more generic" just doesn't work:

Set<Class<?>> singletonSet = new HashSet<Class<Object>>(); // type mismatch

What is going on? Why is Set<Object> assignable to Set<?> and Set<Class<Object>> isn't assignable to Set<Class<?>>?


I always find a way around these kinds of problems but in this case I really want to know why this isn't allowed and not a work-around.

dacwe
  • 43,066
  • 12
  • 116
  • 140
  • 2
    possible duplicate of [java generics covariance](http://stackoverflow.com/questions/2660827/java-generics-covariance) – Voo May 25 '12 at 10:19
  • @dacwe i am sorry dear if i troubled you.you have given me a great answer,but all of a sudden you have deleted the post,i was just about to mark that as an accepted answer.this is regarding the jni question.i didn't know how to contact you,that is why i am commenting here.sorry for the inconvenience caused-Thanks and Regards-johnkrishna – johnkrishna May 29 '12 at 11:14
  • @dacwe This is a six days late answer http://stackoverflow.com/a/10823527 It is a fitting answer though – Michael Buen May 30 '12 at 20:24

5 Answers5

6

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:

  1. Innermost: Is Object compatible to ? Extends Object ? Sure it is.

  2. 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>>();
Michael Buen
  • 38,643
  • 9
  • 94
  • 118
  • Generics related compiler errors finally make sense. Thanks for the great explanation! – Somu May 30 '13 at 12:28
5

Generics are not covariant in Java. This question may help you:

java generics covariance

Community
  • 1
  • 1
Matthias Meid
  • 12,455
  • 7
  • 45
  • 79
0

I tried the following in eclipse under jdk1.6.0_22:

Set<Class<?>> singletonSet = new HashSet<Class<Object>>();

compiler gave me the following:

Type mismatch: cannot convert from HashSet<Class<Object>> to Set<Class<?>>

I changed it to this and it worked:

HashSet<Class<Object>> singletonSet = new HashSet<Class<Object>>();

Seems like the generic of generics must have the same class of collections and the same class of the collected object.

This seems to be a rule in java, maybe because of high error prone at runtime which could cause major crashes. So writing it in this way it will also work:

HashSet<Class<?>> singletonSet = new HashSet<Class<?>>();
GingerHead
  • 8,130
  • 15
  • 59
  • 93
  • This will work, but it is the type of thing that the OP is avoiding, it's obvious this will work `HashSet> singletonSet = new HashSet>();` They are exactly the same type. What he(and any disciplined coder) is trying to achieve is, is to `program against an interface`, not directly on concrete class – Michael Buen May 30 '12 at 20:23
0
Set<Class<?>> 

This says: a Set of classes of a any type

HashSet<Class<Object>>

This says: a HashSet of classes which have to be of type Object - so only Object.class objects are allowed - which doesn't make much sense since there is only one class object for Object.

I think the question is still valid, but maybe you could choose a different sample than using Class.

Puce
  • 37,247
  • 13
  • 80
  • 152
-1

Here the answer is that it accepts any kind of the Object type.

Hence you have to write below thing

Set<Class<?>> val = new HashSet<Class<?>>();

here you can store any kind of Object in the Set

Bhavik Ambani
  • 6,557
  • 14
  • 55
  • 86
  • 4
    @tibtof The question was *why* his above code doesn't work. Not *how* he can fix it - two quite different things and the first one is by far more interesting. – Voo May 25 '12 at 10:21
  • @tibtof I just gave the solution for that – Bhavik Ambani May 25 '12 at 10:23
  • 1
    I didn't even want a solution... I want to know *why*. But I already have an answer I can accept, thanks. – dacwe May 25 '12 at 11:07