Generic Cell “implementation”

How do you define a “generic” Cell class to hold any object? You don’t want to define similar classes with duplicate code: StringCell, Pointcell.

class Cell {
    private final Object obj;

    Cell(Object obj) {
        this.obj = obj;
    }

    Object get() {
        return this.obj;
    }
}

Explicit cast

Calling method get() on a Cell returns an Object type, which doesn’t have length method.

jshell> new Cell("chocolate").get().length()
| Error:
| cannot find symbol
| symbol: method length()
| new Cell("chocolate").get().length()
| ^-------------------------------^

The solution

jshell> ((String) new Cell("chocolate").get()).length()
$.. ==> 9

Generic Type = Parametric Polymorphism

Allow a type of method to operate on objects of various types while providing compile-time type safety.

Type parameter section <T1, T2, T3> Type parameters can then be used anywhere within the class/interface.


Generic Classes

Now, you don’t need explicit casting. **The compiler tracks the actual type through the container.

class Cell<T> {
    private final T t;

    Cell(T t) {
        this.t = t;
    }

    T get() {
        return t;
    }

    @Override
    public String toString() {
        return "[" + t + "]";
    }
}

jshell> Cell<String> cell = new Cell<String>("abc")
cell ==> [abc]

jshell> cell.get().length()
$.. ==> 3

Generic Method

jshell> <T> Cell<T> of(T t) { return new Cell<T>(t); }
| created method of(T)

jshell> Cell<String> cell = of("abc")
cell ==> [abc]
class Cell<T> {        // class-level T
    private final T t;
    ...
    static <T> Cell<T> of(T t) {  // method-level T (should use U)
        return new Cell<T>(t);
    }
}

There are actually two different Ts here that just happen to share the same name. The class-level T is the one used by instance fields and methods. The method-level <T> (declared right after static) is a completely separate type variable scoped only to that method.


Substitutability

Remember about generic invariance. Even if A is a subtype of B, Method<A> is not a subtype of Method<B>

<? extends T> and Covariant Rule (for Reading)

Reading: getting a value out of the cell (cell.get)

jshell> void foo(Cell<? extends Burger> cell) {
...> Burger b = cell.get();
...> }

jshell> foo(new Cell<FishBurger>(new FishBurger())) works now!

Covariant Rule

If E1 <: E2 (the element type is a subtype) and S <: T (the container type is a subtype), then S<E1> <: T<? extends E2>.

FishBurger <: Burger (given), and Cell <: Cell (trivially), so Cell<FishBurger> <: Cell<? extends Burger>.

When you call cell.get() on a Cell<? extends Burger>, you always get back something that is a Burger. If the actual cell holds a FishBurger, that’s fine because FishBurger is a Burger.

<? super T> and Covariant Rule (for Writing)

Writing: putting a value into the cell (cell.set).

jshell> void bar(Cell<? super Burger> cell) {
...> cell.set(new FishBurger());
...> }

jshell> bar(new Cell<FastFood>(new Burger())) works now!

Contravariant Rule

If E2 <: E1 and S <: T, then S<E1> <: T<? super E2>.

Burger <: FastFood and Cell <: Cell, so Cell<FastFood> <: Cell<? super Burger>.


Substitutability For Reading and Writing

jshell> void baz(Cell<Burger> cell1, Cell<Burger> cell2) {
...> cell1.swap(cell2);
...> }

baz can only take in Cell<Burger> and nothing else! – too general and you cannot get (or read) from the cell; – too specific and you cannot set (or write) to the cell.

Cell<T> is the strongest (most restrictive) generic type – allowed to only read and write elements of type T with Cell<T>

Invariant Rule

If E1 = E2 (the element types are exactly equal) and S <: T (the container type is a subtype), then S<E1> <: T<E2>.

U = T (given), and Cell <: Cell (trivially) so Cell<U> <: Cell<T>.


Substitutability of Cell for No Access

jshell> void qux(Cell<?> cell) {
...> cell.get().toString();
...> }

Cell<?> is the weakest (least restrictive) generic type – not allowed to access elements of type T, or – at most read it as an Object

No access rule

If S <: T (the container type is a subtype), then S<E> <: T<?> for any E. Cell <: Cell (trivially), so Cell<anything> <: Cell<?>.


The Full Spectrum

Cell<T> — exact type, read and write with type T Cell<? extends T> — accepts subtypes, read only (get returns T) Cell<? super T> — accepts supertypes, write only (get returns Object) Cell<?> — accepts anything, no typed access (get returns Object, can’t set anything except null)