Java - (Generic|Parameterized) type - (Class|Interface|Method) Parametrization

> Procedural Languages > Java

1 - About

Java Generics were invented primarily for implementation of generic collections such as Set and Map. They are also used in single-element containers, such as ThreadLocal and AtomicReference.

You use them only in a container implementation.

Generics enable non-primitive types (classes and interfaces) to be parameters when defining:

Class was generified in release 1.5. The type of a class literal is no longer simply Class, but Class<T>. For example:

  • String.class is of type Class<String>,
  • and Integer.class is of type Class<Integer>.
Advertising

3 - Why ?

Generics allow to abstract over types and add the following two advanatges

  • Stronger type checks at compile time. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find. The compiler can check the type correctness of the program at compile-time.
  • Elimination of cast.

The most common examples are container types, such as those in the Collections hierarchy.

List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3        

Becomes with generic:

List<Integer> myIntList = new LinkedList<Integer>(); // 1' Not only a List, but a List of Integer,
myIntList.add(new Integer(0)); // 2'
Integer x = myIntList.iterator().next(); // 3' -- Cast is gone

4 - Example

The most common examples are container types, such as those in the Collections hierarchy.

  • A Set has a single type parameter, representing its element type;
  • a Map has two, representing its key and value types

5 - Type Variable

Type variables don't exist at run time. See type erasure

5.1 - Parameter

The T in Foo<T> is a type parameter

Type parameter names are single, uppercase letters.

The most commonly used type parameter names are:

Advertising

5.1.1 - Bounded

A method that operates on numbers that only want to accept instances of Number or its subclasses will used bounded type parameters.

5.2 - Argument

String in Foo<String> f is a type argument.

5.3 - Wildcard

The question mark (?), called the wildcard, represents an unknown type.

The wildcard is never used as a type argument for:

  • a generic method invocation,
  • a generic class instance creation,
  • or a supertype.

Others:

public static void process(List<? extends Foo> list) { /* ... */ }
public static void addNumbers(List<? super Integer> list) { /* ... */ }
public static void printList(List<?> list) { /* ... */ }
  • and wildcard capture (when the compiler infers a particular type from the code)
Advertising

5.4 - Question mark (?)

See wildcard

6 - Subtyping

Definition of a custom list interface, PayloadList, that associates an optional value of generic type P with each element.

interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
  ...
}

7 - Runtime

Comes from Collections#checkedCollection

The generics mechanism in the language provides compile-time (static) type checking, but it is possible to defeat this mechanism with unchecked casts. Usually this is not a problem, as the compiler issues warnings on all such unchecked operations. There are, however, times when static type checking alone is not sufficient. For example, suppose a collection is passed to a third-party library and it is imperative that the library code not corrupt the collection by inserting an element of the wrong type.

8 - Type erasure

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

Type variables don't exist at run time. This means that they entail no performance overhead in either time nor space, which is nice. Unfortunately, it also means that you can't reliably use them in casts and instanceOf.

8.1 - Unbounded

When the type parameter T is unbounded, the Java compiler replaces it with Object:

public class Node<T> {
 
    private T data;
    private Node<T> next;
 
   /*....*/
}

becomes

public class Node {
 
    private Object data;
    private Node next;
 /*....*/
}

8.2 - Bounded

The Java compiler replaces the bounded type parameter T with the first bound class (ie Comparable):

public class Node<T extends Comparable<T>> {
 
    private T data;
    private Node<T> next;
 
   /*....*/
}

becomes

public class Node {
 
    private Comparable data;
    private Node next;
 
   /*....*/
}

8.3 - Casts and InstanceOf

Cannot Use Casts or instanceof with Parameterized Types

Because the Java compiler erases all type parameters in generic code, you cannot verify which parameterized type for a generic type is being used at runtime:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

The runtime does not keep track of type parameters, so it cannot tell the difference between an ArrayList<Integer> and an ArrayList<String>.

The most you can do is to use an unbounded wildcard to verify that the list is an ArrayList:

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

9 - Documentation / Reference

lang/java/generic.txt · Last modified: 2018/04/01 16:26 by gerardnico