Java Generics

Introduction

  • The generic type enhances software safety, by using compile-time knowledge to prevent simple type related errors.

  • These generic type or parameter type requires a reference type instead of a primitive type.

The good way to introduce Generics is through an example. In the example below, we would like to get the sum of a list of integers.

The example below provides the traditional implementation before the introduction of Generics

List ints = Arrays.asList( new Integer[] {
  new Integer(1), new Integer(2), new Integer(3)
} );
int s = 0;
for (Iterator it = ints.iterator(); it.hasNext(); ) {
  int n = ((Integer)it.next()).intValue();
  sum += n;
}
assert sum == 6;
  • Without Generics, we cannot indicate the data type of the elements stored in the Array List.

  • It is the coder, rather than the compiler who needs to remember the type of the list element, hence, when we retrieve the element from the list, we have to cast it to the correct data type before using it.

The code below provides the same implementation using Generics:

List<Integer> ints = Arrays.asList(1,2,3);
int sum = 0;
for (int n: ints) { sum += n; }
assert sum == 6;
  • The syntax <T> is called a type parameter. It conveys that the container / generic (e.g. List in this case) is parametrised by the item payload type (e.g. Integer in this case).

  • Type parameters are always reference types and cannot be primitive types.

  • Generics are like containers. With their type parameter, they become “a List of Strings” or “a List of Integers” or “a List of Users”, etc.

Boxing and Unboxing

  • We now let the compiler become aware of the data type of this Array List.

  • In doing so, we do not have to perform type casting (Integer) when extracting an element from the list.

  • We also do not have to call the elemental method to get the element’s primitive data type (intValue()). The compiler is able to infer this.

  • It automatically does boxing and unboxing, i.e., conversion between Java primitive type with its corresponding reference type.

  • This means that generic type parameters are only visible at compile time. They are stripped out by the compiler in the bytecode. However, because of this, two generic variables having the same name but different type parameters compile to the same generic variable name, causing a clash, thus making this syntax illegal and unable to compile. This effect is called Type Erasure.

Benefits

  • The code is cleaner and readable. The compiler ensures data types are compatible.

  • This enhancement has effectively introduced a new kind of type to Java’s type system with Java 5 - by combining the container type and the type parameter.

Bounded Type Parameters

public class NumberBox<T extends Number> extends Box<T> {
  public int intValue() {
    return value.intValue();
  }
}
  • The type <T extends Number> ensures that T can only be substituted with a type that is compatible with the type Number.

  • Thus, the compiler knows that value will definitely have a method intValue() available.

// This is good and will compile
NumberBox<Integer> ni = new NumberBox<>();

// This will not compile
NumberBox<Object> nah = new NumberBox<>();