Java Generics

There are enough tutorials on Java Generics. However, many are either too easy or too difficult to get a clear view of the subject. In this series, we focus on confusing aspects and try to explain them with multiple examples.

If you are new to generics, we suggest you to go through Java Tutorial Trail - Generics as we don’t cover the basics. Even after going through it if certain aspects are not clear, 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 a Generic Type

A generic type is a type with formal type parameters.

    public interface List<E>{
        public void add(E element);
        public E get(int index);
    }

The interface List is a generic type with single type parameter - E. Type parameters can be more than one. 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 scope of type parameters defined at class level is limited to the class. It can referred within the class as method parameters, return type and fields.

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 an instance of ArrayList, we use parameterized type ArrayList

    // for generic type List<E>, the parameterized type is
    new ArrayList<String>();  

To refer the instance of a generic type, we need variable with parameterized type

    
    List<String> strings = new ArrayList<String>();

    // or short form
    List<String> strings = new ArrayList<>();

From Java 1.7 onwards, we can skip actual type argument and short it as new ArrayList<>() as compiler can infer it from the parameterized type on the left side.

Let’s create some objects of generic type Pair,

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

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

In first example, to instantiate generic type Pair<T, U>, we use parameterized type Pair<String, Integer>. It replaces type parameter (T and U) with actual type arguments (String and Integer). In second example, we use Integer as actual type argument for both T and U.

Loosely speaking, to define a generic type use type parameters; to refer a generic type instance (either through variables or fields) use parameterized type.

Naming the Type Parameters

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

  • T - Type and U,V etc. - 2nd, 3rd types
  • 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 be used in method declarations, but its scope is limited to the method where it is declared.

    // add elements of array of a type to collection of same type
    // <T> is type parameter 
    public <T> void addAll(T[] elements, Collection<T> coll) {
        for (T element : elements) {
            coll.add(element);
        }
    }

The addAll() method adds all elements of array to the collection. It defines type parameter <T> and uses it in methods’ formal parameters - T[] elements and Collection<T> coll. In for loop, all the items of elements array are added to Collection 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

Both the parameters - elements and coll is of same type T, and to invoke the method, we should use array and List of same type otherwise compiler throws error. For example, when try to pass array of String and list of integer code will not compile.

Syntax to define 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 referred as fields and methods` formal parameters, body and return type.