Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics of generics and more generic <?> assignments

Tags:

java

generics

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.

like image 785
dacwe Avatar asked May 25 '12 09:05

dacwe


People also ask

What is the purpose of generics?

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. An entity such as class, interface, or method that operates on a parameterized type is a generic entity. Why Generics?

What is generics in Java?

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. An entity such as class, interface, or method that operates on a parameterized type is called ...

Are generics just for collection types?

Although generics are not just for collection types the majority of usage is within collections and so most of the examples in this section will use Collections. When we talk about generics or a generic type what we are actually talking about is a parameterized type.

What is generics in C++?

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.


2 Answers

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>>();
like image 149
Michael Buen Avatar answered Nov 09 '22 17:11

Michael Buen


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

java generics covariance

like image 32
Matthias Meid Avatar answered Nov 09 '22 16:11

Matthias Meid