Java Generics

There are enough tutorials on Java Generics; however, they are either too basic or too difficult to get a clear understanding of generics. This tutorial focuses on confusing aspects and clarify them with multiple examples.

If you are new to generics, we suggest you to go through Java Tutorial Trail - Generics as this tutorial doesn’t explains the basics. After going through it you may end with some doubts; then try this tutorial.

Except enum types, anonymous inner classes and exception classes, all other types can be generic, but we use classes and interfaces as examples.

Define Generic Type

It is easy to define a generic type. A generic type is a type with formal type parameters. For example, the class Box<T> is a generic type with single type parameter - T.


    public class Box<T> {

        private T t;

        public void set(T t) {
            this.t = t;
        }

        public T get() {
            return t;
        }
    }

A generic type can have multiple type parameters. The class Pair, shown below, is a generic type with two type parameters - T, U.


    public class Pair<T, U> {

        private T first;
        private U second;

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

        public T getFirst() {
            return first;
        }

        public U getSecond() {
            return second;
        }
    }

The syntax to define Generic class is:

modifiers class className<T1, T2, ..., Tn> { }

The type parameter section is delimited by angle brackets < > which follows the class name and contains the type parameters (also called type variables) T1, T2, …, and Tn delimited by comma.

The type parameters defined at class level can referred within the class as class fields, constructor or method parameters, return type and local variables. The scope of type parameters defined at class level is limited to the class. In the above example, the type parameter T is referred in the constructor parameter.

 
 

How to use Generic Type

To use a generic type, either we have to create a new instance or refer the instance using a variable. Creating and using the generic type is called invocation; to do that, we have to use parameterized type. In parameterized type, the type parameters such as E are replaced with actual type arguments such as String

To create or refer an instance of generic type, use parameterized type.

For example to create instance of Box<T> that can hold a String, use parameterized type new Box<String> and assign it to a variable with parameterized type Box<String> stringBox.


    Box<String> stringBox = new Box<String>();

    // or short form (Java 1.7 onwards)
    Box<String> stringBox = new Box<>();

Similarly create some instances of generic type Pair<T,U>,


    Pair<String, Integer> oddPairs = new Pairs<>("hello",10);

    Pair<Integer, Integer> intPairs = new Pair<>(1,7);

In Pair<String, Integer> we used type arguments String and Integer for type parameters T and U. The pair can hold a String and Integer. For the second one Pair<Integer, Integer>, we used Integer as type argument for both the type parameters T and U. The pair can holds two integer.

Loosely speaking,

  • to define a generic type use type parameters. Example: Pair<T, U>.
  • to refer a generic type instance (variables, fields etc.,) use parameterized type. Example: Pair<Integer, Integer>.
 
 

Generic Terminology

For clarity, let’s recap the terminologies introduced so far.

  • generic type List<T>
  • type parameter/type variables <T>
  • type argument <String>
  • parameterized type List<String> list;
  • formal parameter of a method void foo(String x)
  • formal type parameter Bar<T>, formal type parameter of method void foo(T x)

Naming the Type Parameters

There are no hard and fast rules about the type parameter names; however, by convention type parameter names are single, uppercase letters. Most commonly used ones are:

  • T - Type, U for 2nd Type and V for 3rd Type etc.,
  • E - Element
  • K - Key
  • V - Value
  • N - Number
  • R - Return type

Some more examples of generic types and its instantiation and reference.

Generic Types (uses Type Parameters)Instantiation or Reference (uses Parameterized Type)
List<E>ArrayList<String>
Map<K,V>TreeMap<String, Date>
Function<T,R>Function<String, Boolean>

Define Generic Method

Just like generic type declaration type parameters can also be used in method declarations, but its scope is limited to the method where it is declared.


    // <T> is type parameter
    
    // add items of array of a type to a collection of same type 

    public <T> void addAll(T[] elements, Collection<T> coll) {
        for (T element : elements) {
            coll.add(element);
        }
    }

The addAll() method adds all elements of an array to the collection. It defines type parameter <T> and uses it in method’s formal parameters - T[] elements and Collection<T> coll. The method is called as below,


    String[] alphabets = {"a","b"};
    List<String> stringList = new ArrayList<>();
    
    addAll(alphabets, stringList);

    List<Integer> intList = new ArrayList<>();    
    addAll(alphabets, intList);      // error

The parameters elements and coll are of same type T, and to invoke the method we should use array and List of same type. In the second addAll() call we try to pass an array of String and list of integer and it results in error.

Syntax to define a generic method is:

modifiers <T1, T2, ..., Tn> returnType methodName(formal parameters) { }

The type parameter list - <T>, <E>, or <T, U> - is placed just before the method’s return type. The scope of type parameters is restricted to the method where it is defined. The type parameters defined at method level can be referred in method parameters, method return type and the method body.

As explained earlier, to refer a generic object we use parameterized type with actual type arguments such as List<String> list;; however, to call a generic method there is no need to specify actual type as the compiler infers the type argument from the types of the actual arguments. For example to call addAll() method, we simply call it as addAll(alphabets, list). Compiler infers the most specific type argument that will make the call type-correct.

Summary

  • to define a generic type, use type parameters - class Pair<T, U> { }
  • to refer a generic type (define a variable or field), use parameterized type - Pair<String, Integer> oddPairs;
  • type parameters can be defined at class level or method level.
    • When defined at class level, its scope is the entire class.
    • when defined at method level its scope is restricted to the method.
  • type parameters can be used as fields, method’s formal parameters, local variables and return type.

The next chapter explains generic sub types.