When you work with generic classes, you need to learn a few rules about inheritance and subtypes. Let’s start with a situation which many programmers find unintuitive. Consider a class and a subclass, such as Employee and Manager. Is Pair<Manager> a subclass of Pair<Employee>? Perhaps surprisingly, the answer is “no.” For example, the following code will not compile:
Manager[] topHonchos = . .
Pair<Employee> result = ArrayAlg.minmax(topHonchos); // ERROR
The minmax method returns a Pair<Manager>, not a Pair<Employee>, and it is illegal to assign one to the other.
In general, there is no relationship between Pair<S> and Pair<T>, no matter how S and T are related (see Figure 8.1).
Figure 8.1 No inheritance relationship between pair classes
This seems like a cruel restriction, but it is necessary for type safety. Suppose we were allowed to convert a Pair<Manager> to a Pair<Employee>. Consider this code:
var managerBuddies = new Pair<Manager>(ceo, cfo);
Pair<Employee> employeeBuddies = managerBuddies; // illegal, but suppose it wasn’t
employeeBuddies.setFirst(lowlyEmployee);
Clearly, the last statement is legal. But employeeBuddies and managerBuddies refer to the same object. We now managed to pair up the CFO with a lowly employee, which should not be possible for a Pair<Manager>.
You can always convert a parameterized type to a raw type. For example, Pair<Employee> is a subtype of the raw type Pair. This conversion is necessary for interfacing with legacy code
Can you convert to the raw type and then cause a type error? Unfortunately, you can. Consider this example:
var managerBuddies = new Pair<Manager>(ceo, cfo);
Pair rawBuddies = managerBuddies; // OK
rawBuddies.setFirst(new Fite(“. . .”)); // only a compile-time warning
This sounds scary. However, keep in mind that you are no worse off than you were with older versions of Java. The security of the virtual machine is not at stake. When the foreign object is retrieved with getFirst and assigned to a Manager variable, a ClassCastException is thrown, just as in the good old days. You merely lose the added safety that generic programming normally provides.
Finally, generic classes can extend or implement other generic classes. In this regard, they are no different from ordinary classes. For example, the class ArrayList<T> implements the interface List<T>. That means an ArrayList<Manager> can be converted to a List<Manager>. However, as you just saw, an ArrayList<Manager> is not an ArrayList<Employee> or List<Employee>. Figure 8.2 shows these relationships.
Source: Horstmann Cay S. (2019), Core Java. Volume I – Fundamentals, Pearson; 11th edition.