Question

Consider the following classes:

interface Notifiable {

}

class NotifiableImpl1 implements Notifiable {

}

class NotifiableImpl2 implements Notifiable {

}

class NotifiableImpl3 implements Notifiable {

}

It is normal that the following code works:

Set<Notifiable> set = new HashSet<>();
set.add(new NotifiableImpl1());
set.add(new NotifiableImpl2());
set.add(new NotifiableImpl3());

However, the following code does not work:

Set<? extends Notifiable> set2 = new HashSet<>();
set2.add(new NotifiableImpl1());
set2.add(new NotifiableImpl2());
set2.add(new NotifiableImpl3());

I understand it does not work, because it should only be possible to add one concrete subtype of Notifiable to set2, but how does it come then, that the following code also does not work?

Set<? extends Notifiable> set2 = new HashSet<>();
set2.add(new NotifiableImpl1());

And maybe more interesting, why does the following work?

Set<Notifiable> set = new HashSet<>();
set.add(new NotifiableImpl1());
set.add(new NotifiableImpl2());
set.add(new NotifiableImpl3());

Set<? extends Notifiable> set3 = set;
Was it helpful?

Solution

The first code is perfectly valid. Since you can have a reference of super type hold a subclass object as in below assignment:

Notifiable notifiable = new NotifiableImpl1();

The same way you can add a NotifiableImpl object to a Set<Notifiable>. That set can hold object of any subtype.

Second code is not valid as per the PECS rule. Basically a Set<? extends Notifiable> is a producer of Notifiable, and not a consumer. You can't add any subtype object to it. That set can actually hold a reference to a HashSet<NotifiableImpl1>, and you can't add a NotifiableImpl2 object to such set. It would fail at runtime, if it was allowed by compiler. In fact you can't add anything except null to such types, because the actual type that the Set will hold is still unknown to the compiler.

Third code is also valid. A Set<? extends Notifiable> means a Set of an unknown type that extends from Notifiable. So, in the following code:

Set<Notifiable> set = new HashSet<>();
Set<? extends Notifiable> set3 = set;

You're simply assigning a concrete parameterized type reference to a bounded wildcard type. Since Notifiable is a valid substitute for ? extends Notifiable, that assignment perfectly makes sense.

The reason behind existence of the wildcard types is to allow a single reference point to different kinds of objects. This is the general polymorphic rule. Without wildcard it won't be valid for a Set<Notifiable> to point to a HashSet<NotifiableImpl1>, simply because generic types are invariant.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top