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.
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.
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.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With