Relevant Reading Material

Generics

Generic Types

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.

listinterfaceadt
stringlistinterfaceadt
boollistinterfaceadt
xlistinterfaceadt

and here is the code for each class/interface

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> {


  /**
   * {@inheritDoc}
   */
  @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> {

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

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

  /**
   * {@inheritDoc}
   */
  @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;

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

  /**
   * {@inheritDoc}
   */
  @Override
  public Integer size() {
    return 1 + this.rest.size();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public Boolean isEmpty() {
    return false;
  }

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

}

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>.

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 lecture 2) 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();

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

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

The above method will work for any parameterized list.

List<Object> is not the same as List<?>.
listwildcard

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

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 replaces 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!

Mutation

The ability to alter, in-place, the state of an object.

Setters

In the same way that we have getter methods that return the value of an objects field, we also have setter methods that allow us to update, in-place, the value of an objects field.

public class Name {
    private String first;
    private String last;

    public Name(String first, String last){
        this.first = first;
        this.last = last;
    }

    public String getFrist() {
        return this.first;
    }

    public String getLast() {
        return last;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public void setLast(String last) {
        this.last = last;
    }
}

The first thing to notice here is the keyword void. void is used as a return value to a method to indicate that this method does not return any value, it updates the object on which this method is called. So the caller of a setter method should not expect a new value back. The caller of a setter methods should expect the internal state of the object to change based on what the setter method documentation.

Walk though an example and its evaluation to show the effect of a setter.

Circular data and the need for null

We would like to implement a circular, singly linked list.

circularsinglylinkedlistex

Here is our first attempt

circularsinglylinkedlistclass

Lets try and write examples. Can we create an example for a circular list with 1 element, e.g.,

circularsinglylinkedlist

What is the second argument to the constructor?

  Cell singleElementList = new Cell("A", ???);

What to we write in the place of ???? We would like to put singleElementList but we cannot since we are in the process of creating it and we are not done creating singleElementList. We need to somehow

  1. partly create singleElementList

  2. set the next field of our partly created object to itself

Itemization!

We need an itemization such that we capture the empty case and the non-empty case.

itemizationinsteadofnull

Then we can

Cell singleElementList = new NMT("A", new MT());
singleElementList.setNext(singleElementList);

This means we have to create a corresponding MT class with all the methods of the super class (Cell) implemented (some will throw exceptions, i.e., getNext()) for all such situations.

Java provides a special value called null that can be used to denote such empty (uninitialized or absent-value) cases.

What do you think is the type of null?

Using null

We can go back to our original design and use null in the location of ???

Cell singleElementList = new Cell("A", null);
singleElementList.setNext(singleElementList);

What happens if we run the following code?

Cell singleElementList = new Cell("A", null);
singleElementList.setNext(singleElementList);
singleElementList.toString();                  // ?
Walk through the execution of toString()

What happens when we run this code

Cell singleElementList = new Cell("A", null);
singleElementList.setNext(singleElementList);
singleElementList.toString();                  // ?