Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do nested type arguments work?

Tags:

java

generics

Why does the declaration

Set<Set<String>> var = new HashSet<Set<String>>();

work but the declaration

Set<Set<String>> var = new HashSet<HashSet<String>>();

choke?

I'm aware that 'top level' (not sure if that's the correct phrase here) generics in a declaration play by different rules than those inside the pointy brackets, but I'm interested to learn the reason. Not an easy question to google, so I thought I'd try you guys.

like image 722
Big Bird Avatar asked Dec 02 '10 18:12

Big Bird


People also ask

What is a nested type?

A nested type is a type defined within the scope of another type, which is called the enclosing type. A nested type has access to all members of its enclosing type. For example, it has access to private fields defined in the enclosing type and to protected fields defined in all ascendants of the enclosing type.

What are 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.


2 Answers

It's because you could circumvent the type system if it were allowed. The property you desire is called covariance. If collections were covariant then you'd be able to do this:

Set<Set<String>> var = new HashSet<HashSet<String>>();

var.add(new TreeSet<String>());

A TreeSet is a type of Set, and so static type checking would not prevent you from inserting a TreeSet into var. But var expects HashSets and HashSets only, not any old type of Set.

Personally, I always write your first declaration:

Set<Set<String>> var = new HashSet<Set<String>>();

The outer class needs to have a conrete implementation, but there's usually no need to nail down the inner class to HashSet specifically. If you create a HashSet of Sets you are good to go. Whether you then proceed to insert a series of HashSets into var is your choice later in the program, but no need to restrict the declaration.


For what it's worth, arrays in Java are covariant, unlike the collection classes. This code will compile and will throw a runtime exception instead of being caught at compile time.

// Arrays are covariant, assignment is permitted.
Object[] array = new String[] {"foo", "bar"};

// Runtime exception, can't convert Integer to String.
array[0] = new Integer(5);
like image 120
John Kugelman Avatar answered Nov 14 '22 08:11

John Kugelman


The reason is that Set<Set<String>> is not equivalent to Set<HashSet<String>>! A Set<Set<String>> may contain any type of Set<String>, while a Set<HashSet<String>> may only contain HashSet<String>.

If Set<Set<String>> set = new HashSet<HashSet<String>>() were legal, you could also do this without any error:

Set<HashSet<String>> setOfHashSets = new HashSet<HashSet<String>>();
Set<Set<String>> set = setOfHashSets;
set.add(new TreeSet<String>());
HashSet<String> wrong = set.iterator().next(); // ERROR!

It is, however, legal to use a bounded wildcard here:

Set<? extends Set<String>> set = setOfHashSets;

This is allowed because now, the type of object the set contains is ? extends Set<String>... in other words, "some specific but unknown class that implements Set<String>". Since you don't know exactly what the specific type of Set<String> it is allowed to contain is, you aren't allowed to add anything to it (except null)... you might be wrong, leading to an error later like in my first example.

Edit:

Note that the "top level" generics you refer to in your question are called parameterized types, meaning types that take one or more type parameters. The reason that Set<Set<String>> set = new HashSet<Set<String>>() is legal is that HashSet<T> implements Set<T> and is therefore a subtype of Set<T>. Note, however, that the type parameter T must match! If you have another type S that is a subtype of T, a HashSet<S> (or just a Set<S> even) is not a subtype of Set<T>! I explained the reason for that above.

This is exactly the situation here.

Set<Set<String>> set = new HashSet<Set<String>>();

If we replace Set<String> with T here, we get Set<T> set = new HashSet<T>(). It's easy to see, then, that the actual type arguments involved match and that the type on the right side of the assignment is a subtype of the type on the left.

Set<Set<String>> set = new HashSet<HashSet<String>>();

Here we have to replace Set<String> and HashSet<String> with T and S respectively, where S is a subtype of T. With that done, we get Set<T> set = new HashSet<S>(). As I described above, HashSet<S> is not a subtype of Set<T>, so the assignment is illegal.

like image 37
ColinD Avatar answered Nov 14 '22 09:11

ColinD