Relevant Reading Material

Chapters from How to Design Classes (HtDC)

  • Chapters 30, 31, 32, 33

Generics

List of

Abstract over Types.

Recall our List implementation.

listinterfaceadt

Our List consits of Integer elements. What if I want a list of String, or Boolean or some other type that we do not know yet.

intlistinterfaceadt

List of integers

stringlistinterfaceadt

List of strings

boollistinterfaceadt

List of booleans

Let’s play "spot the differences"

  • In our interfaces

    1. last() returns a value whose type differs, Integer, String, Boolean

    2. addToEnd() takes an argument whose type differs, Integer, String, Boolean

    3. elementAt() returns a value whose type differs, Integer, String, Boolean

    4. add()

      1. takes an argument whose type differs, Integer, String, Boolean

      2. returns a value whose type differs, the interface is named List in all 3 cases but in each case List denotes something different.

  • In Cons

    1. first stores a value whose type differs, Integer, String, Boolean

    2. rest stores a value whose type differs, the interface is named List in all 3 cases but in each case List denotes something different.

A parameter for our Classes and Interfaces

Java allows us to parameterize over our classes and interfaces. Much like we can have parameters for a method, we can also have parameter to a class or interface. Parameters on classes and interfaces are called type parameters.

The parameter however has a different meaning.

  • For a method parameter

    1. we provide the type of the parameter

    2. we provide a name for the parameter

    3. the body of the method can use (manipulate/call methods) on the named parameter

    4. we pass a value when calling the method

  • For a class or interface type parameter

    1. we provide a name only

    2. the body of the class or interface can use this name as a type not a value.

    3. we pass a type when creating an instance or extending/implementing the class/interface

Recall from CS5001

;; List of Integers (LoI) is one of
;; - empty
;; - (cons Integer LoI)
;; List of String (LoS) is one of
;; - empty
;; - (cons String LoS)
;; List of Boolean (LoB) is one of
;; - empty
;; - (cons Boolean Loi)
;; List<X> is one of
;; - empty
;; - (cons X List<X>)

In UML our generic list interface

genericListInterface

The Java source for our generic list interface.

public interface List<X> {

  /**
   * Return the number of elements in this list.
   *
   * @return the number of elements in the list
   */
  Integer size();

  /**
   * Given an element prepending to this list
   *
   * @param element the element to add
   * @return the list with the element prepended
   */
  List<X> add(X element);


  /**
   * Return true if this list is empty,
   *  and false otherwise.
   *
   * @return true if this list is empty
   *         and false otherwise
   */
  Boolean isEmpty();


  /**
   * Given an index return the element
   * at that index in the list.
   *
   * @param index index to use
   * @return the element at that index
   * @throws GenericListException
   */
  X elementAt(Integer index) throws GenericListException;
}

The X in List<X> is called a type parameter. We are parameterizing our class over a type. Like method parameters we can have more than 1, for example if we are building a Map ADT we can have one type parameter for the key and one type parameter for the value of the map, Map<K,V>.

Type paremeters range over reference types only.

Getting back are type specific lists from our generic interface

Now that we have a generic interface, how can we get back our Integer, String and Boolean list implementations?

We can have the classes that implement List<X> specify the concrete type that they specialize on.

AIntegerList.java
package genericlist.v1.integerlist;

import genericlist.v1.List;

/**
 * Created by therapon on 6/20/16.
 */
public abstract class AIntegerList implements List<Integer> {
  @Override
  public List<Integer> add(Integer element) {
    return new Cons(element, this);
  }
}
Empty.java
package genericlist.v1.integerlist;

import genericlist.v1.GenericListException;

/**
 * Created by therapon on 6/20/16.
 */
public class Empty extends AIntegerList {
  @Override
  public Integer size() {
    return 0;
  }


  @Override
  public Boolean isEmpty() {
    return true;
  }

  @Override
  public Integer elementAt(Integer index) throws GenericListException {
    throw new GenericListException("Called elementAt on empty");
  }
}
Cons.java
package genericlist.v1.integerlist;

import genericlist.v1.GenericListException;

/**
 * Created by therapon on 6/20/16.
 */
public class Cons extends AIntegerList {

  private Integer first;
  private AIntegerList rest;

  public Cons(Integer first, AIntegerList rest) {
    this.first = first;
    this.rest = rest;
  }

  @Override
  public Integer size() {
    return 1 + getRest().size();
  }


  @Override
  public Boolean isEmpty() {
    return false;
  }

  @Override
  public Integer elementAt(Integer index) throws GenericListException {
    if (index < 0 || index > size() - 1) {
      throw new GenericListException("Index out of bounds");
    }

    if (index == 0) {
      return getFirst();
    } else {
      return getRest().elementAt(index - 1);
    }
  }


  public Integer getFirst() {
    return first;
  }

  public AIntegerList getRest() {
    return rest;
  }
}

How to re-create and use our integer list now

Main.java
package genericlist.v1.integerlist;

import genericlist.v1.List;

/**
 * Created by therapon on 6/20/16.
 */
public class Main {

  public static void main(String[] args) {
    List<Integer> empty = new Empty();  // Preferred!
    AIntegerList empty2 = new Empty();

    List<Integer> oneElement = empty.add(1);


  }



}

We are exposing our constructors from our implementation classes. We will come back and update our design so that we can hide our concrete classes and their constructors.

You can easily repeat this pattern to provide implementations for

  1. list of strings

  2. list of integers

  3. list of any reference type that you want.

Can we generalize further?

Observe that our list interface and its implementations for

  1. integer list

  2. string list

  3. boolean list

Do not perform any operation(s) on their elements that is specific to the type of the lists element.

There are no calls to methods specific to

  1. Integer

  2. String

  3. Boolean

In our first attempt to get back our behaviour for integer, string and boolean list we had to write 3 implementations. If we had to do this for more reference types we would have to write a new implementation for each concrete type that we want to create a list.

Can we do better?

Generic List

Our previous implementations took the type parameter X and forced it to be a specific, known, type, i.e., Integer, String and Boolean. Since the implementation of our list does not rely on specific methods that are found on concrete types, we can parameterize our implementation further and get a generic list for any reference type.

xlistinterfaceadt
Figure 1. Generic List
package edu.neu.ccs.cs5004.sp16.lecture05.GenericLists;



public interface List<X> {

  /**
   * Return the number of elements in this list.
   *
   * @return the number of elements in the list
   */
  Integer size();

  /**
   * Given an element prepending to this list
   *
   * @param element the element to add
   * @return the list with the element prepended
   */
  List<X> add(X element);


  /**
   * Return true if this list is empty,
   *  and false otherwise.
   *
   * @return true if this list is empty
   *         and false otherwise
   */
  Boolean isEmpty();


  /**
   * Given an index return the element
   * at that index in the list.
   *
   * @param index index to use
   * @return the element at that index
   * @throws GenericListException
   */
  X elementAt(Integer index) throws GenericListException;
}
package edu.neu.ccs.cs5004.sp16.lecture05.GenericLists;

public abstract class AList<X> implements List<X> {

  @Override
  public List<X> add(X element) {
    return new Cons<X>(element, this);
  }
}
package edu.neu.ccs.cs5004.sp16.lecture05.GenericLists;

public class Empty<X> extends AList<X> {

  @Override
  public Integer size() {
    return 0;
  }

  @Override
  public Boolean isEmpty() {
    return true;
  }

  @Override
  public X elementAt(Integer index) throws GenericListException {
    throw new GenericListException("Called elementAt on empty list");
  }

}
package edu.neu.ccs.cs5004.sp16.lecture05.GenericLists;

public class Cons<X> extends AList<X> {

  private X first;
  private List<X> rest;

  public Cons(X first, List<X> rest) {
    this.first = first;
    this.rest = rest;
  }

  @Override
  public Integer size() {
    return 1 + this.rest.size();
  }

  @Override
  public Boolean isEmpty() {
    return false;
  }

  @Override
  public X elementAt(Integer index) throws GenericListException {
    if (index.equals(1)) {
      return this.first;
    } else {
      return this.rest.elementAt(index - 1);
    }
  }

}

We can no use the name of the type parameter X inside List (as well as in Empty and Cons) in locations that expect/require a type, i.e., as a return type to a method signature, as a type for a method formal parameter or the type of a class' field.

Given this new definition of List<X> how do we now create lists of integers, strings, booleans etc.

  List<Integer> integerList = new Empty<Integer>(); // OR
  List<Integer> integerList = new Empty<>();        // if you are in Java 7 or above.

In fact we can create lists of lists as well.

  List<List<Integer>> integerListList = new Empty<>();
  List<List<List<Integer>>> integerListListList = new Empty<>();

  integerListList.add(new Empty<Integer>().add(3)); // OK

  integerListList.add(4); // ERROR: compiler rejects this line!

Generic Methods

Notice that our implementation of generic list List<X> is missing the public static method that we used to create instances on List.

Java allows us to have methods (static or otherwise) that introduce their own type parameters. Here is the factory method but this time with generics

 public static <Y> List<Y> emptyList() {
    return new Empty<Y>();
  }

The first <Y> introduces a new type variable; similar to the <X> on the definition of List<X>. We need to first introduce the new type parameter and then we can use it in the signature and body of the method. We can introduce one or more type parameters for methods.

Our emptyList() method declares a new type parameter Y and claims that it will return as a result a List<Y>; that is a list parameterized by that type parameter that we introduced. Notice that emptyList() takes no arguments and uses the same type parameter Y to create an instance of Empty. The instance of Empty will be specialized to Y, that is it is not just any Empty it is an Empty for a specific type that will be inferred at the point in our code that we call emptyList()

 List<Integer> intergerList = List.emptyList();  // Y is Integer
 List<List<String>> intergerList = List.emptyList();  // Y is List<String>

Bounded Type Parameters

Having a type parameter gives us a lot of flexibility. Sometimes however we would like to control the set of types that we would like to allow for our type parameter. This is useful in situations were

  • We rely on a method to be called on variables typed at our type parameter, e.g., calling equals() works an any type, but calling size() requires that the type parameter is some subtype of List

  • Our code only works for certain types and not all types

For example if we would like to have a generic list that only handles Shapes (from previous lectures) so that we can add methods like totalArea to our list. Then we can bound are type parameters to only accept subtypes of Shape

public interface List<X extends Shape> {

  public static <Y extends Shape> List<Y> emptyList() {
    return new Empty<Y>();
  }

  // ... same code as before ...
}

We can add bounds to type parameters on classes, interfaces and methods.

Note that the use of the keyword extends when defining bounds refers to

  1. either extends Shape, if Shape is a class

  2. or implements Shape, if Shape is an interface.

We can also specify multiple bounds by adding & followed by the bound type’s name

public interface List<X extends Shape & Scaleable & Moveable> {

  public static <Y extends Shape> List<Y> emptyList() {
    return new Empty<Y>();
  }

  // ... same code as before ...
}
If we add multiple bounds and one of them is a class, that bound needs to go first. So if Shape is a class and Scaleable and Moveable are interfaces the above code compiles. public interface List<X extends Scaleable & Shape & Moveable …​ does not compile.

Inheritance and Generics

Looking at our previous List implementation we know from OO (and this is supported by Java) that we can have the static type of a variable be a superclass of the dynamic type, e.g.,

  AList l1 = new Empty();

Assume that

numbersandints

Unfortunately, even though it feels "natural" to say

List<Number> l1 = new Empty<Integer>(); // Number is a super class of Integer

the preceding line signals a compiler error stating

Type mismatch cannot convert from type Empty<Integer> to List<Number>

Even if we try

Empty<Number> l1 = new Empty<Integer>(); // Number is a super class of Integer

We get a similar compile time error

Type mismatch cannot convert from type Empty<Integer> to Empty<Number>

The reason:

genericsinheritance

Subtyping does work with generic classes when the type parameter is the same. Notice how Empty<Integer> is a subtype of AList<Integer>.

It also works when we have more than one type parameter, as long as the inherited type parameter stays the same, the subtype relationship holds

subtypegenerics

The Wildcard ?

There are cases where we do not know the type to use for the type parameter or we do not care to provide a name for the type parameter. In these cases we can use the special character ? instead

Upper Bound Wildcards

 public static Number add(List<? extends Number> input) {
     //...
 }

The static method add takes a list of elements that are required to be subtypes of Number. In the body of the methods we can use elements of the list input as type Number and call any methods available at type Number.

Unbound Wildcards

We generalize over all types. Compare

 public static String asStringListObj(List<Object> list) {
    String result = "";
    for (Object elem: list) {
      result += elem + " ";
    }
    return result;
  }

asStringListObj works for arguments of type List<Object>,

 List<Integer> li = List.createEmpty();
 List<Object> lo = List.createEmpty();

 asStringListObj(lo);  // works!
 asStringListObj(li);  // COMPILE TIME ERROR!

If we however use ?,

 public static String asStringList(List<?> list) {
    String result = "";
    for (Object elem: list) {
      result += elem + " ";
    }
    return result;
  }

The above method will work for any parameterized list.

 List<Integer> li = List.createEmpty();
 List<Object> lo = List.createEmpty();

 asStringList(lo);  // works!
 asStringList(li);  // works as well!
List<Object> is not the same as List<?>.
listwildcardextends

Lower bounds

We can also set lower bounds on a type parameter but only when using ?.

public static void addNumbers(List<? super Integer> list) {
    // ...
}

The method addNumbers is allowed to add any value of type Integer or its supertypes, e.g., Number and Object.

We can use ? and lower bounds to create relationships between generic types.

listwildcard

Wildcard Guidelines

  • In type parameters refer to type parameters that are used by the code as "inputs"

    1. For a method copy(src,dest) src is an in parameter

  • Out type parameters refer to type parameters that are used by the code as "outputs"

    1. For a method copy(src,dest) dest is an out parameter

  • An "in" variable is defined with an upper bound wildcard, extends

  • An "out" variable is defined with a lower bound wildcard, super

  • If your code uses methods defined in Object on "in" variable, use unbound wildcard, i.e., ?

  • If your code uses a variable as both "in" and "out" do not use a wildcard.

We will see more examples in the upcoming lectures.

Type erasure

Generics do not exist at runtime!

The Java language and JVM implement generics using type erasure. This means that the generics that we write exist statically; they are erased at runtime.

  1. Bounds are replaces with their lower or upper bound type

  2. Fixed type parameters are replaced with Object

  3. The compiler inserts type casts in order to preserve type safety

  4. Generates code to preserve polymorphism in extended generic types

To see examples of some of these re-writes, check the Java Tutorial on Generics.

You have been warned!

Java util package

Let’s have a look at the java.util package and use some of the commonly used classes.