Acid Rider
Acid Rider

Reputation: 1677

Java wildcard bounded generics

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

Answers (1)

davidxxx
davidxxx

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

Related Questions