Relevant Reading Material

Chapters from How to Design Classes (HtDC)

  • Chapters 30, 31, 32, 33

Parametric polymoprhism and Java Generics

Parametric polymorphism is the ability to write data or methods/functions generically such that we can handle values in exactly the same way without relying on their type(s).

Another way to think about parametric polymorphism is the ability to abstract over types instead of values. For example, when we design a method the body of the method is parameterized over the inputs (parameters).

  public Integer myAdd(Integer a, Integer b) {
      return a + b;
  }

The myAdd method can add any two Integer s.

  public boolean equals(Object other) { ... }

The equals method can check for equality between the currently executing object this and some other Object called other.

We would also like to be able to parameterize over our types as well. We have seen something similar in CS5001 with abstract functions and abstract data definitions, recall LoF<X> and map which had the signature map: [X → Y] Lof<X> → LoF<Y>.

The Java language feature that allows us to abstract over reference types is called generics.

Pair

Consider our Posn class. The Posn class is essentially a pair—​a data definition that holds two values—​that we use to capture cartesian coordinates.

It is typically, and in fact sometimes very usefull, to be able to capture a pair in our code and specialize that to our specific case. For example

  • use our pair as a cartesian point

  • use our pair as our internal pair that maps a key to a value in our priority queue implementation

So we would like to create a more general class that allows us to represent a pair of values that we will call first and second. The generality that we want to achieve here is that we can store any value for first and second and we want to keep our nice type properties that Java provides.

Let’s start with an example that although allows us to store any value in our pair, it does not maintain the type properties for our values.

Non Generic Pair
/**
 * Represents a pair of values.
 */

public class Pair {

  private Object first;
  private Object second;

  public Pair(Object first, Object second) {
    this.first = first;
    this.second = second;
  }

  public Object getFirst() {
    return first;
  }

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

  public Object getSecond() {
    return second;
  }

  public void setSecond(Object second) {
    this.second = second;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Pair pair = (Pair) o;

    if (getFirst() != null ? !getFirst().equals(pair.getFirst()) : pair.getFirst() != null)
      return false;
    return getSecond() != null ? getSecond().equals(pair.getSecond()) : pair.getSecond() == null;
  }

  @Override
  public int hashCode() {
    int result = getFirst() != null ? getFirst().hashCode() : 0;
    result = 31 * result + (getSecond() != null ? getSecond().hashCode() : 0);
    return result;
  }

  @Override
  public String toString() {
    return "Pair{"
        + "first=" + first
        + ", second=" + second
        + '}';
  }


  public static void main(String[] args) {
    Pair p1 = new Pair(1, "a");   (1)
    Pair p2 = new Pair(new Posn(1,1), true);


    Object w = p1.getFirst(); (2)
    Object x = p1.getSecond();
    Object y = p2.getFirst();
    Object z = p2.getSecond();

    Integer i = (Integer) w; (3)
    String a = (String) x;
    Posn p = (Posn) y;
    Boolean b = (Boolean) z;

  }
}
1 We create instances of Pair and provide values that have specific types.
2 Getting any value out of a Pair we are now stack with a compile-time type of Object
3 To get back to the more specific compile-time type we need top cast. YUCK! This also means we need to remember what type of a value did we add to which instance of Pair or add extra code to check the runtime-type before we cast and then cast.

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.

Method formal parameters Class/Interface type parameters
  1. Method signature provides type and parameter name

    • e.g., public List add(Integer newElement)

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

    • e.g., if (newElement.equals(this.getFirst())) { …​ }

  3. we pass a value when calling the method

    • e.g., myList.add(100)

  1. we provide a name only

    • e.g., public interface List<X> { …​ }

    • class Empty<X> { …​ }

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

    • e.g., public X getElement(),

    • X tmp = this.getFirst()

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

    • e.g., List<Posn> intList = new Empty<Posn>();

    • List<Posn> intList = new Empty<>(); Java 7 and above can figure out the type in some cases.

/**
 * Represents a pair that holds two values.
 *
 * @param <X> type for the first value.
 * @param <Y> type for the second value.
 */
public class Pair<X,Y> { (1)
  private X first; (2)
  private Y second;

  /**
   * Creates a pair from the two given values.
   *
   * @param first value to be stored as the first element of the pair.
   * @param second value to be stored as the second element of the pair.
   */
  public Pair(X first, Y second) { (3)
    this.first = first;
    this.second = second;
  }

  /**
   * Get the first element of the pair.
   * @return the first element
   */
  public X getFirst() { (4)
    return first;
  }

  /**
   * Set the first element of the pair.
   *
   * @param first the new value for the first element.
   */
  public void setFirst(X first) {
    this.first = first;
  }

  /**
   * Get the second element of the pair.
   *
   * @return the second element
   */
  public Y getSecond() {
    return second;
  }

  /**
   * Set the second element of the pair.
   *
   * @param second the new value for the second element
   */
  public void setSecond(Y second) {
    this.second = second;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Pair<?, ?> pair = (Pair<?, ?>) o;

    if (getFirst() != null ? !getFirst().equals(pair.getFirst()) : pair.getFirst() != null)
      return false;
    return getSecond() != null ? getSecond().equals(pair.getSecond()) : pair.getSecond() == null;
  }

  @Override
  public int hashCode() {
    int result = getFirst() != null ? getFirst().hashCode() : 0;
    result = 31 * result + (getSecond() != null ? getSecond().hashCode() : 0);
    return result;
  }

  @Override
  public String toString() {
    return "Pair{"
        + "first=" + first
        + ", second=" + second
        + '}';
  }

}
1 X and Y are type parameters. Type parameters are enclosed in <> and come immediately after the class/interface. The scope of X and Y is the whole class. They are similar to normal parameters in that we are going to have to provide a type for X and Y. Read <> as of so Pair<X,Y> can be read as Pair of X and Y.
2 We can use a type parameter in the same way that we would use a reference type, as the type of a class field.
3 We can use a type parameter in the same way that we would use a reference type, as the type of an argument.
4 We can use a type parameter in the same way that we would use a reference type, as the return type of a method.
Type parameters range over reference types only.
Parameter type names are typically 1 character, and it is a capital character.

Using our Generic Pair class

Using (create instances and calling methods on these instances) a generic class is similar to a non-generic class with some differences.

Creating an instance of a generic class requires us to provide specific types for each of the type parameters.

 Pair<Integer,Integer> coord1 = new Pair<Integer, Integer>(1,2); (1)
 Pair<Integer,Integer> coord2 = new Pair<>(1,2);    // PREFERED  (2)
1 The compile time time for coord1 needs to provide actual reference types for each type parameter. The call to Pair 's constructor also needs to provide the reference types for each type parameter.
2 Since Java 7, we can now use the diamond <> on the right hand side of an assignment and the Java compiler will auto populate the reference type for each type parameter from the compile-time type on the left hand side of the assignment.

With Generics the Java compiler now remembers what type(s) of value(s) we have set at the time of object creation and will then return the specific type for each value inside of a Pair

 public static void main(String[] args) {
    Pair<Integer, String> p1 = new Pair<>(1, "a");
    Pair<Posn,Boolean> p2 = new Pair<>(new Posn(1,1), true);


    Integer w = p1.getFirst();
    String  x = p1.getSecond();
    Posn    y = p2.getFirst();
    Boolean z = p2.getSecond();
  }

No casts!

Once we have created an instance of Pair and provided reference types for each type parameter, the Java compiler will check our usage of our instance to ensure that our code does not violate any of the restrictions imposed by our types.

 Integer xCoord = coord1.getFirst();
 coord1.setFirts(10);

 coord1.setFirst("A"); // COMPILE TIME ERROR

The last line in our preciding code snippet will signal a compile-time error. The compile-time error indicates that we cannot use a String value in the place of an Integer value.

The flexibility of Java generics allows us to now create Pair s that can contain any reference type values for first and second

Pair<Integer,String> roman1 = new Pair<>(1, "I");
Pair<Integer,String> roman2 = new Pair<>(2, "II");
Pair<Integer,String> roman3 = new Pair<>(3, "III");

Pair<Integer, List> keyList =
  new Pair<>(42, List.createEmpty("A").add("Bar").add("Beer"));

And something even more interesting is that our Pair can have Pair s as its first and second as well.

Pair<Integer, Pair<Integer, Integer>> node =
  new Pair<>(10, new Pair<>(5, 15));

Designing with Generic Classes

Generic classes can be very useful. Sometimes however we would like to use a generic class to capture information from our problem domain. The information we would like to capture, can be modelled with an existing generic class but we would like to further add specific behaviour that is not generic in nature.

Consider our Pair example. It is pretty easy to see that we can replace our Posn class with uses of our Pair class where the type parameters are both Integer or Double. But if we would also like to have specific behaviour that relates to Posn, i.e., distance between Posn s, we do not want to add that behaviour to Pair. That will take away from our nice generic Pair class. Instead we would like to wrap or extend our Pair class, and specialize it for Posn.

For this example we will use Integer for the values of our Posn class.

public class Posn extends Pair<Integer, Integer> { (1)

  public Posn(Integer x, Integer y) {
    super(x, y);
  }

  /**
   * Getter for property 'x'.
   *
   * @return Value for property 'x'.
   */
  public Integer getX() {  (2)
    return getFirst();
  }

  /**
   * Setter for property 'x'.
   *
   * @param x Value to set for property 'x'.
   */
  public void setX(Integer x) {
    setFirst(x);
  }

  /**
   * Getter for property 'y'.
   *
   * @return Value for property 'y'.
   */
  public Integer getY() {
    return getSecond();
  }

  /**
   * Setter for property 'y'.
   *
   * @param y Value to set for property 'y'.
   */
  public void setY(Integer y) {
    setSecond(y);
  }


  public Double distanceTo(Posn other) {   (3)
    return Math.sqrt(Math.pow(this.getX() - other.getX(), 2) +
        Math.pow(this.getY() - other.getY(), 2));
  }


  @Override
  public String toString() {
    return "Posn{" + this.getX() + ", " + this.getY() + "}" ;
  }
}
1 Our Posn class extends Pair<Integer, Integer> because we want to always have Integer values for x and y
2 Wrapper methods like these will allow us to reuse our Posn<Integer,Integer> with a small code change with existing clients. It is also typically considered good style to provide methods names that match the problem domain like getX and getY instead of getFirst and getSecond
3 Now our Posn class has a distance method that takes another Posn. The method is specific to Posn s and takes as an argument a specific value, another Posn. distance has not connection or relation to Pair<>.

List of

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

Recall from CS5001

;; An LoI is one of
;; - empty
;; - (cons Integer LoI)
;; An LoS is one of
;; - empty
;; - (cons String LoS)
;; An LoB is one of
;; - empty
;; - (cons Boolean LoB)
;; List<X> (LoF<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> { (1)
  /**
   * 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); (2)


  /**
   * 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;
}
1 X is a type parameter.
2 We are using X just like any other type name, as the type of the argument element and as part of the return type List<X>. These uses of X refer to the same X introduced on the first line, public interface List<X> { …​ }

Our new generic list is now parameterized over any reference type, much like our List<X> from Racket. We can not create any list that contains values of a specific reference type from one interface.

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

A generic method (static or non-static), like a generic class or interface, allows a method to abstract over types. Consider our Pair<X,Y> class and for simplicity here, lets try and write a PairUtil class that will contain utility methods for Pair<X,Y>. For instance a method that takes 2 generic pairs and tells us if they are the same.

public class PairUtil {

    public static <X,Y> boolean pairEquals(Pair<X,Y> p1, Pair<X,Y> p2) {
        return p1.getFirst().equals(p2.getFirst())
            && p1.getSecond().equals(p2.getSecond());
    }
}

In the case of generic methods, the new type parameters are defined before the return type, in our case right before boolean. These type parameters are available

  1. in the method’s signature

  2. in the method’s body

Let’s use our pairEquals method.

Pair<String, Integer> p1 = new Pair<>("I", 1);
Pair<String, Integer> p2 = new Pair<>("II", 2);

PairUtil.<String, Integer> pairEquals(p1, p2);

Observer that when we call our pairEquals method immediately before the method name we provide the concrete types for the type arguments <X,Y>.

The Java compiler tries to help out (like int he case of Pair<>) and will allow us to omit <String, Integer> in PairUtil.<String, Integer> pairEquals(p1,p2), e.g.,

Pair<String, Integer> p1 = new Pair<>("I", 1);  // Java infers the type for us
Pair<String, Integer> p2 = new Pair<>("II", 2);

PairUtil.pairEquals(p1, p2);    // Java infers the types for us.

Adding back our static factory method

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

Let’s consider our static factory methods for List as we had it before we added generics

public interface IntegerList {

    static IntegerList createEmpty() {
        return new Empty();
    }

    // elided code
}

Our static factory method was responsible for creating an empty IntegerList, that is an empty list of a specific type. Now that we added generics, we would like to have a similar method that will allow us to create a list of any type, Integer, String, Posn, etc.

Similar to how we abstracted over types for List we can abstract over types for a method. This applies to both static and non-static methods.

In order to abstract over a method and allow the methods to apply to any type, we need to introduce a new type parameter much like we did for List. The Java language allows us to introduce new type parameters for a method as part of the method’s signature, immediately before the method’s return type.

For example, here is the generic factory method

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

The first <Y> introduces a new type parameter; similar to the <X> on the definition of List<X>. This type parameter <Y> is available for emptyList method only. 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 and their scope is limited to

  • the method’s signature

  • the method’s body

Why do I need <Y> why can’t I use <X> from List<X>

You noticed that for our static method we introduced a new type parameter <Y> and we named it Y to show that it is different that the existing type parameter <X> introduced by our interface List<X>.

The reason for having a new type parameter <Y> is because we want our method to be generic. We want our code to be able to call this method and provide whatever type is appropriate. Further more, we are calling emptyList before we even have an instance of List<X>; that is the point of emptyList to create an empty list for whatever type we want. This is similar to our pairEquals method from earlier.

Recall

 List<Integer> lint = new Empty<Integer>();

In this example, lint uses Integer for X. We typically say

  • type parameter when we talk about X in List<X>

  • type argument when we talk about Integer in List<Integer> lint = new Empty<Integer>();

In the case of our static factory method, our intention is to use this method to create new lists of any type. Our static method is generic, it abstracts over types of lists that we can create. Our static factory method is called before a concrete constructor for a List is called, e.g., Empty 's or Cons 's constructors.

We therefore need our method to be able to accept any type argument. This type argument will be passed on to our call of the appropriate constructor, in this case Empty. Therefore our method needs a type parameter to capture the appropriate type argument and create the specific list requested by the client code.

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, it is not just any Empty it is an Empty for a specific type that will be provide (or inferred) at the point in our code that we call emptyList().

The specific reference type that will replace Y will be given at the time that we call createEmpty, e.g.,

 List<Integer> intergerList = List.<Integer>emptyList();  // Y is Integer
 List<Integer> intergerList = List.emptyList();  // equivalent if Java can infer the type

 List<List<String>> intergerList = List.<List<String>> emptyList();  // Y is List<String>
 List<List<String>> intergerList = List.emptyList();  // equivalent if Java can infer the type

Java’s type inferencer for type parameters

Java has a type inferencer. The type inferencer tries to detect and infer the appropriate type arguments for type parameters.

Recall

List<Integer> lint = new Empty<>(); // infers Integer for Empty<Integer>

List<String> lstr = List.createEmpty(); // infers <String> for List.<String> createEmpty();

Sometimes, however, Java’s type inferencer cannot infer the most appropriate type argument and it will cause errors.

List<String> lstr = List.createEmpty().add("a"); // Compile time error!

The preceding line has a compile-time error that states:

Incpomaptible types:

 Required: java.lang.String
 Found:    java.lang.Object

The details for why Java’s inference cannot infer the appropariate types has to do with how generics are implemented in Java which is not the main focus of the course. We will mention some information on the implementation of Generics in Java later on.

The work around for these situtaions are:

  1. Provide the type arguments in your code

  2. Break you statement into multiple statements to help the inferencer

For example, we can provide the type parameter that we want

List<String> lstr = List.createEmpty().add("a"); // Compile time error!
List<String> lstr = List.<String> createEmpty().add("a"); // Fixed!

Or break our statement into multiple statements making it easier for Java’s type inferencer to correctly detect the appropriate type arguments

List<String> lstr = List.createEmpty().add("a"); // Compile time error!
List<String> lstr = List.<String> createEmpty();
lstr.add("a"); // Fixed!

You typically want to follow the first pattern, it is clear what you want the type arguments to be despite the fact that the syntax might look a little odd.

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

For example Java has the following hierarchy

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

Bounded Type Parameters

The notion of subtyping in the presence of generics affects our design and our use of polymorphism.

Lets consider the following situation that uses Shape s and a List<Shape>.

We would like to write a utility class that given a list of any shapes, returns the total area occupied by all the shapes in the list.

Our first attempt to writing this method

public interface ShapeListUtil {

  static Double totalArea(List<Shape> shapes) {
    Double acc = new Double(0);

    for (int i = 1; i <= shapes.size(); i++) {
      acc = acc + shapes.elementAt(i).area();
    }

    return acc;
  }
}

Let’s write some tests

public void totalArea() throws Exception {
    List<Shape> slist = List.EmptyList();
    Posn p = new Posn(1,1);
    slist =
        slist
            .add(new Rectangle(p, 10, 10))
            .add(new Rectangle(p, 5,5));

    System.out.println(ShapeList1.totalArea(slist));
}

Well this one works!

How about this test

 public void totalArea() throws Exception {
    Posn p = new Posn(1,1);
    List<Rectangle> rlist =  List.EmptyList();
    rlist =
        rlist
            .add(new Rectangle(p, 10, 10))
            .add(new Rectangle(p, 5,5));

    System.out.println(ShapeList1.totalArea(rlist));  // COMPILE TIME ERROR!


  }

Our second test does not even compile. We get the compile-time error message:

totalArea (genericlist.notes.List<genericlist.shapes.Shape>)   cannot be applied to
          (genericlist.notes.List<genericlist.shapes.Rectangle>)

The subtype relationship between generic classes does not define List<Rectagle> as a subtype of List<Shape>.

This is odd since we actually have a test, our first test, that is in fact a list of Rectangle s and it works! How can we write out totalArea method such that it works for all List s that contain Shape types?

The answer is to use bounded type parameters. A bounded type parameter in Java allows us to specify an upper bound—​a type that we can safely use regardless of the actual parameter type.

An upper bound instructs the Java language that we will accept any type in our type hierarchy that is our upper bound or any of its subtypes.

Lets re-write our example using an upper bound. The syntax for an upper bound is

  • <X extends Shape>

This means that we want to generalize our type parameter X such that X must be a subtype of Shape. If we try and use a type parameter that is not a subtype of Shape the compiler will issue a compile-time error. An upper bound on our type parameter allows us to rely on any operation defined on the upper bound type, in this case Shape, within our generic class/method body.

static <X extends Shape> Double totalArea(List<X> shapes) {
    Double acc = new Double(0);

    for (int i = 1; i <= shapes.size(); i++) {
      acc = acc + shapes.elementAt(i).area(); // We can rely on area() since our upper bound is Shape
    }

    return acc;
  }

Our generic method totalArea is now abstracted over all List<X> where X can range over all subtypes of Shape, so what we have is a method that accepts any of the following types

  • List<Shape>

  • List<Rectagle>

  • List<Circle>

  • and any other subtype of Shape that we might add later on

Think of the upper bound as way to restrict the types that your code will accept for X.

Note that the use of the keyword extends when defining bounds works for both

  1. extends Shape, if Shape is a class

  2. implements Shape, if Shape is an interface.

Read it as suptype even though we type the word extends.

We can also specify multiple bounds by adding & followed by the bound type’s name if our code is going to depend on behaviour defined in any of these upper bounds.

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

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

  public Double totalArea();
}
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.

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

Observe that in our last version of totalArea even though we have a type parameter X the code does not use X at all. We could use a wildcard instead, e.g.,

static Double totalArea(List<? extends Shape> shapes) {
    Double acc = new Double(0);

    for (int i = 1; i <= shapes.size(); i++) {
      acc = acc + shapes.elementAt(i).area(); // We can rely on area() since our upper bound is Shape
    }

    return acc;
  }

and the implementations are equivalent.

Unbound Wildcards

There is a much stronger argument for the existence of ?.

Consider

 // print each list element separated by a space
 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<?>. List<?> is the mother of generic list types. Like Object is to non-generic reference types.
genericmother

Let’s take a step back and recap

Before Generics subtyping in Java allowed us to use a instances of a subtype in the place of a super type, e.g.,

absubtypes

We could then write code that would use instances of B as A

 A a = new B();
 B b = new B();

 A a2 = b;

We relied on this behaviour extensively!

Now with generics however

List<A> la = List.<B> createEmpty(); // COMPILE TIME ERROR

List<B> lb = List.<B> createEmpty();
List<A> la = lb;                     // COMPILE TIME ERROR

The notion of subtypes in the presence of generics follows different rules. We know that for any generic class, e.g., List<X> the most common supertype is List<?>. We also know that subtyping for generic types requires

  1. the type parameter to be the same

  2. the parameterized class, e.g. List, Empty and Cons, to be in a subtype relationship

Recall

genericsinheritance

Also recall

subtypegenerics

Lower bounds

In the same manner that we can use our upper bounds on ? to restrict the unknown type to be a specific type or a subtype of that type we can also use lower bounds to restrict the unknown type to be a specific type or a super type of that type.

Think for example of a method that you would like to add to a Rectangle to a generic list, but you want this method to work for List<Rectangle>, List<Shape> and List<Object>. Another way of putting it is you would like this method to work on any list that can hold Rectangles.

The syntax for a lower bound is

  • <? super Rectangle>

public static void addRectangles(java.util.List<? super Rectangle> recs, Integer maxWidth, Integer maxHeight){
    Posn p = new Posn(0,0);
    for (int i = 1; i < maxWidth; i++) {
        for (int j = 1; j < maxHeight; j++){
            recs.add(new Rectangle(p, i, j));
        }
    }
}

Here are some uses of our method

List<Object> lobj = new ArrayList<Object>();
ShapeList1.addRectangles(lobj, 3, 3);


List<Shape> lshape2 = new ArrayList<Shape>();
ShapeList1.addRectangles(lshape2, 3, 3);

List<Rectangle> lrec = new ArrayList<Rectangle>();
ShapeList1.addRectangles(lrec, 3, 3);
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 and Collection

The java.util package contains utility classes and interfaces. The package has been evolving with each Java version to add commonly used classes/interfaces. One of the most commonly used framework (collection of types) is the Collection framework.

Collections Framework

A collection here refers to a group of objects. Objects that are part of a collection are referred to as elements. Under the collections framework we have

  1. Interfaces that define the behaviour of collections, e.g., Set<E>, List<E>, Queue<E> etc.

  2. Concrete classes that provide a general-purpose implementation of the interface(s) that you can use directly, e.g., ArrayList, LinkedList, HashSet

  3. Abstract classes that implement the interface(s) of the collection framework that you can extend in order to create specialized data structures applicable for your problem.

The goals of the Collections Framework is to

  1. Reduce programmer effort by providing the most common data structures

  2. Provide a set of types that are easy to use and extend

  3. Provide flexibility through defining a standard set of interfaces for collections to implement

  4. Improve program quality through the use and reuse of tested implementations of common data structures

Overview of Collections Framework Hierarchy

UML class diagram is intentionally incomplete. The class diagram below contains some of the Collections Framework interfaces and classes and for each type only some of its signatures. For a complete list of types and methods see the javadoc for java.util.

collectionshierarchy
 public static void main(String[] args) {

    System.out.println("**** List Examples ****");
    List<Integer> lint1 = new ArrayList<>();
    List<Integer> lint2 = new LinkedList<>();

    for (int i = 0; i < 10 ; i++) {
      lint1.add(i);
      lint2.add(i);
    }

    System.out.println(lint1);
    System.out.println(lint2);

    System.out.println(lint1.isEmpty());
    System.out.println(lint2.isEmpty());

    System.out.println(lint1.size());
    System.out.println(lint2.size());

    System.out.println(lint1.contains(0));
    System.out.println(lint2.contains(1));

    System.out.println("**** Set Examples ****");
    Set<String> sset = new HashSet<>();
    sset.add("a");
    sset.add("b");
    System.out.println(sset);
    sset.add("a");
    System.out.println(sset);
    System.out.println(sset.contains("a"));
    System.out.println(sset.contains("x"));

    Set<String> sset2 = new HashSet<>();
    sset2.add("b");
    sset2.add("a");
    System.out.println(sset2.equals(sset));

    sset.add("x");
    sset.add("y");
    sset2.add("y");
    sset2.add("x");

    System.out.println(sset.equals(sset2));

    System.out.println("**** Queue Examples ****");
    Queue<String> q1 = new ArrayDeque<>();
    System.out.println(q1.size());
    System.out.println(q1);
    q1.add("a");
    q1.add("b");
    q1.add("c");
    System.out.println(q1.size());
    System.out.println(q1);
    System.out.println(q1.peek());   // same as calling element()
    System.out.println(q1);
    q1.poll();                      // same as calling remove()
    System.out.println(q1);

    Deque<String> dq1 = new ArrayDeque<>();
    dq1.addFirst("x");
    dq1.addFirst("y");
    dq1.addFirst("z");
    dq1.addLast("a");
    dq1.addLast("b");
    dq1.addLast("c");
    System.out.println(dq1);
    System.out.println(dq1.peekFirst());
    System.out.println(dq1.peekLast());

    dq1.pollFirst();
    System.out.println(dq1);

    dq1.pollLast();
    System.out.println(dq1);
  }

Let’s take a closer look at some of the classes that implement List<E>

  1. ArrayList. More compact, less memory.

  2. LinkedList. More efficient if high number of insertions/deletions (no shifting)

  3. Vector. Like ArrayList but better for concurrent code.

  4. Stack. Extends Vector. Prefer the Dequeue interface (ArrayDequeue) instead.

Another common interface from the java.util collection is Map which allow us to maintain a mapping between keys and values. The Map interface provides three collection views

  1. a set of the map’s keys

  2. a collection of the map’s values

  3. a set of key-value mappings as a Map.Entry type. We will talk about this weird looking type name in our next class.

 public static void main(String[] args) {
    Map<Integer, String> arabicToRoman = new HashMap<>();
    arabicToRoman.put(1, "I");
    arabicToRoman.put(2, "II");
    arabicToRoman.put(3, "III");
    arabicToRoman.put(4, "IV");
    arabicToRoman.put(5, "V");

    System.out.println(arabicToRoman);
    Set<Integer> keys = arabicToRoman.keySet();
    System.out.println(keys);

    Collection<String> values = arabicToRoman.values();
    System.out.println(values);

    for (Map.Entry<Integer,String> entry : arabicToRoman.entrySet()) {
      System.out.println(entry);
    }
  }