Reputation: 1677
I am struggling with Java 8 wildcard generics.
Assume a generic class (from Core Java book) called Pair<T>
class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
Assume the following class hierarchy:
base Employee (top of hierarchy), then
Manager extends Employee, and then
Executive extends Manager
The following code below works but I don't understand why it is allowed.
Pair<? super Manager> pm2 =
new Pair<>(
new Employee(1,"Yuri"), // Employee is super of Manager
new Executive()); // Executive is not super of Manager
// why Executive is allowed in above Pair<T> ?
Employee ex1 = (Employee) pm2.getFirst(); // OK
Manager ex2 = (Manager) pm2.getSecond(); // OK
Executive ex3 = (Executive) pm2.getSecond(); // why is allowed?
I dont understand why Executive works above given it is not a super-type but it is a sub-type of Manager.
Is it because Java 8 compiler converts ? super Manager to Object and therefore anything will be permitted?
Upvotes: 1
Views: 105
Reputation: 131326
As you declare :
Pair<? super Manager> pm2 = ...
the Pair
methods that accept the generic can substitute the generic by Manager
and subclasses (in your case Executive
) of that.
So logically when you invoke a method using the generic you will get this result :
Pair<? super Manager> pm2 = ...;
pm2.setFirst(new Executive()); // compile
pm2.setFirst(new Manager()); // compile
pm2.setFirst(new Employee()); // doesn't compile
Finally it is like if you had not used any wildcard :
Pair<Manager> pm2 = ...;
pm2.setFirst(new Executive()); // compile
pm2.setFirst(new Manager()); // compile
pm2.setFirst(new Employee()); // doesn't compile
So in your example, the lower bounded wildcard that you use is helpless.
In fact it is worst as you use it both for putting and getting things from the generic type while the lower bounded wildcard is designed to put things in, not to get things from.
Whereas the casts that you have to perform and that defeat the generic purpose (type-safety) :
Employee ex1 = (Employee) pm2.getFirst(); // OK
Manager ex2 = (Manager) pm2.getSecond(); // OK
Executive ex3 = (Executive) pm2.getSecond(); // why is allowed?
So how do we need to use Pair<? super Manager>
?
As we want that the generic collection Pair
may be assignable to other Pair
types to "put" things in.
The very common use case is declaring a method that accepts a generic type <? super>
.
But in this case we want to restrict the unknown type to be a specific type or a super type of to prevent "putting" things that could compromise the type safety of the generic type passed as parameter.
For example suppose that you need a method that accepts a Pair
of Manager
instances and that puts things in this Pair
.
You don't want that clients method may pass a Pair<Executive>
otherwise it could break the generic safety if we add a Manager
in because a Manager
is not necessary an Executive
instance. Here is the main role of the lower bounded wildcard.
Sample code to illustrate :
public void putStuff(Pair<? super Manager> managers) {
if (...){
managers.setFirst(new Manager());
}
else if (...){
managers.setFirst(new Executive());
}
}
Now only types or super types of Manager
for a Pair
generic type can be passed :
putStuff(new Pair<Manager>(...)); // compile
putStuff(new Pair<Employee>(...)); // compile
putStuff(new Pair<Executive>(...)); // doesn't compile
Upvotes: 1