Extending Generic Types – Generics

Extending Generic Types

A non-final generic type can be extended. Example 11.5 shows that the generic interface IBiLink<E> extends the generic interface IMonoLink<E>, and that the generic class BiNode<E> extends the generic class MonoNode<E> and implements the generic interface IBiLink<E> (see Figure 11.1).

Click here to view code image

interface IBiLink<E> extends IMonoLink<E> {
  // …
}
class BiNode<E> extends MonoNode<E> implements IBiLink<E> {
  // …
}

The compiler checks that the formal type parameters of the superclass in the extends clause can be resolved. In the case above, the formal type parameter E, which is specified for the subclass, is also used as the type parameter for the superclass and is used to constrain the interface to the same type parameter. This dependency ensures that an invocation of the subclass will result in the same actual type parameter being used by the superclass and for the interface.

Click here to view code image

BiNode<Integer> intBiNode = new BiNode<>(2020, null, null);
MonoNode<Integer> intMonoNode = intBiNode;        // (1)
Integer iRef = intMonoNode.getData();             // Integer with int value 2020
MonoNode<Number> numMonoNode = intBiNode;         // (2) Compile-time error!

The assignment at (1) is type-safe, as the parameterized class BiNode<Integer> is a subtype of the parameterized class MonoNode<Integer>. It is important to note that the superclass and the subclass are parameterized with the same type parameter; otherwise, the subtype relationship between the superclass and the subclass does not hold. We get a compile-time error at (2) because the parameterized class BiNode<Integer> is not a subtype of the parameterized class MonoNode<Number>. Subtype relationships for generic types are discussed in a later section (p. 579).

Figure 11.1 Extending Generic Types

Example 11.5 Extending Generic Types

Click here to view code image

interface IBiLink<T> extends IMonoLink<T> {
  void       setPrevious(IBiLink<T> previous);
  IBiLink<T> getPrevious();
}
class BiNode<E> extends MonoNode<E> implements IBiLink<E> {
  private IBiLink<E>  previous;    // Reference to previous node
  BiNode(E data, IBiLink<E> next, IBiLink<E> previous) {
    super(data, next);
    this.previous = previous;
  }
  @Override public void setPrevious(IBiLink<E> previous) {
    this.previous = previous;
  }
  @Override public IBiLink<E> getPrevious() { return this.previous; }
  @Override public String toString() {
    return (this.previous == null? “” : this.previous + “, “) +
            this.getData() +
           (this.getNext() == null? “” : “, ” + this.getNext());
  }
}

Example 11.5 showed examples of generic types being extended to create new generic subtypes. We can extend a non-generic type to a generic subtype as well:

Click here to view code image

class AbstractNode { /* … */ }                      // A non-generic supertype
class SimpleNode<E> extends AbstractNode { /* … */ }// A generic subtype

We can also extend concrete parameterized types to specialized non-generic subtypes:

Click here to view code image

class IntegerBiNode extends BiNode<Integer> {         // A non-generic subtype
  IntegerBiNode(Integer data, IntegerBiNode next, IntegerBiNode previous) {
    super(data, next, previous);
  }
  //…
}

Note that a subtype can inherit only one parameterization of the same generic interface supertype. Implementing or extending a parameterized type fixes the parameterization for the subtype and its supertypes. In the declaration below, the subtype WeirdNode<E> tries to implement the interface IMonoLink<Integer>, but at the same time, it is a subtype of the interface IMonoLink<E> which the superclass Mono-Node<E> implements:

Click here to view code image

class WeirdNode<E> extends MonoNode<E> implements IMonoLink<Integer> { // Error!
  //…
}

There is great flexibility in extending reference types, but care must be exercised to achieve the desired result.

Leave a Reply

Your email address will not be published. Required fields are marked *