Java Generics Tutorial

1- Why Java Generics?

Generics is a concept which is put into Java since version 5. Before introducing the concept of generics, we see a snippet of Java code prior to version 5.
In this example,  ArrayList is a list in which  you can add, delete, modify the list, and access to the elements of the list.
BeforeJ5Example.java
package org.o7planning.tutorial.generics;

import java.util.ArrayList;

public class BeforeJ5Example {

   public static void main(String[] args) {

       // Create a list, aim to contain usernames
       ArrayList userNames = new ArrayList();

       // Add username to list.
       userNames.add("tom");
       userNames.add("jerry");


       // You accidentally add an element not String to the list.
       // (This is allowed).
       userNames.add(new Integer(100));


       // And get the first element
       // It is an Object (But you know it is a String)
       // ==> Tom
       Object obj1 = userNames.get(0);

       // Cast to String.
       String userName1 = (String) obj1;

       System.out.println("userName1 = " + userName1);


       // And get the second element
       // (You know it is a String)
       // ==> jerry
       String userName2 = (String) userNames.get(1);

       System.out.println("userName2 = " + userName2);

     
       // Get the 3rd element (Actually it is an Integer).
       // (Error casts happen here).
       String userName3 = (String) userNames.get(2);

       System.out.println("userName3 = " + userName3);
   }

}
A situation in Java older version 5:

You create an  ArrayList object for the purpose of only containing elements with the type of  String , however, you add to this list an element that not a  String type at somewhere in the program (This is entirely possible), while you get out of that element and cast to a  String type, an exception will be thrown.
  • TODO (Image)
Java 5  put into concept of  Generics. With the help of  Generics, you can create an  ArrayList object which only allows to contain elements with 
type of String, and not allows to contain elements with other types. 
J5Example.java
package org.o7planning.tutorial.generics;

import java.util.ArrayList;

public class J5Example {

   public static void main(String[] args) {
       
       // Create a list, aim to contain usernames
       ArrayList<String> userNames = new ArrayList<String>();

        // Add string to list
       userNames.add("tom");
       userNames.add("jerry");

        // You can not add an element not a String
       userNames.add(new Integer(100)); // Compile Error!

       // You do not need to cast.
       String userName1 = userNames.get(0);

       System.out.println("userName1 = " + userName1);

   }

}
When you create an   ArrayList <String> object, it only contains elements with type of String, the Java compiler does not allow this object to contains the elements that are different from String.

2- Generic Class, Interface

2.1- Class Generics

The example below defines a  generics class.  KeyValue is a generics class that contains pairs of key and value.
KeyValue.java
package org.o7planning.tutorial.generics.ci;

public class KeyValue<K, V> {

   private K key;
   private V value;

   public KeyValue(K key, V value) {
       this.key = key;
       this.value = value;
   }

   public K getKey() {
       return key;
   }

   public void setKey(K key) {
       this.key = key;
   }

   public V getValue() {
       return value;
   }

   public void setValue(V value) {
       this.value = value;
   }

}
K, V in   KeyValue <K, V> class  is called generics parameter which  is a certain type of reference. When using this class, you must determine the specific parameter.

Take the example of using   KeyValue class
KeyValueDemo.java
package org.o7planning.tutorial.generics.ci;

public class KeyValueDemo {

   public static void main(String[] args) {

     
       // Create KeyValue object.
       // Integer: Phone Number (K = Integer)
       // String: Name (V = String)
       KeyValue<Integer, String> entry = new KeyValue<Integer, String>(12000111, "Tom");
       
       // Java understands that the return type is a Integer
       // (K = Integer)
       Integer phone = entry.getKey();
       
       // Java understands that the return type is a String
       //  (V = String).
       String name = entry.getValue();
       
       System.out.println("Phone = "+ phone+" / name = "+ name);
   }

}
Running Example:

2.2- Inheritance Generics class

A class extended from a generics class  can specify parameter type for generics, retain generics parameters  or add  generics parameters .

Example 1:

PhoneNameEntry.java
package org.o7planning.tutorial.generics.ci;

// This class extends KeyValue<K,V>
// And specify K, V
// K = Integer (Phone Number)
// V = String (Name)
public class PhoneNameEntry extends KeyValue<Integer,String> {

   public PhoneNameEntry(Integer key, String value) {
       super(key, value);
   }

}
Example use PhoneNameEntry:
PhoneNameEntryDemo.java
package org.o7planning.tutorial.generics.ci;

public class PhoneNameEntryDemo {

   public static void main(String[] args) {

       PhoneNameEntry entry = new PhoneNameEntry(12000111, "Tom");


       // Java understands that the return type is Integer.
       Integer phone = entry.getKey();

       // Java understands that the return type is String.
       String name = entry.getValue();

       System.out.println("Phone = " + phone + " / name = " + name);

   }

}

Example 2:

StringAndValueEntry.java
package org.o7planning.tutorial.generics.ci;


// This class extends KeyValue<K,V>
// Specify the parameter K is String.
public class StringAndValueEntry<V> extends KeyValue<String, V> {

   public StringAndValueEntry(String key, V value) {
       super(key, value);
   }

}
Example use StringAndValueEntry class:
StringAndValueEntryDemo.java
package org.o7planning.tutorial.generics.ci;

public class StringAndValueEntryDemo {

   public static void main(String[] args) {

       // (Emp Number, Employee Name)
       // V = String (Employee Name)
       StringAndValueEntry<String> entry = new StringAndValueEntry<String>("E001", "Tom");

       String empNumber = entry.getKey();

       String empName = entry.getValue();

       System.out.println("Emp Number = " + empNumber);
       System.out.println("Emp Name = " + empName);

   }

}

Example 3:

KeyValueInfo.java
package org.o7planning.tutorial.generics.ci;


// This class extends KeyValue<K,V>
// It has added a parameter generics I.
public class KeyValueInfo<K, V, I> extends KeyValue<K, V> {

   private I info;

   public KeyValueInfo(K key, V value) {
       super(key, value);
   }

   public KeyValueInfo(K key, V value, I info) {
       super(key, value);
       this.info = info;
   }

   public I getInfo() {
       return info;
   }

   public void setInfo(I info) {
       this.info = info;
   }

}

2.3- Generics Interface

Interface with Generics:
GenericInterface.java
package org.o7planning.tutorial.generics.ci;

public interface GenericInterface<G> {

 
  public G doSomething();
 
}
For example, an implementation of the interface:
GenericInterfaceImpl.java
package org.o7planning.tutorial.generics.ci;

public class GenericInterfaceImpl<G> implements GenericInterface<G>{

   private G something;
   
   @Override
   public G doSomething() {
       return something;
   }

}

2.4- Java doesn't support generic Throwable

You can not create a generic class which is a descendant of Throwable, because java does not support to create such class.
Error message of compiler:
- The generic class MyException<E> may not subclass java.lang.Throwable
Java do not support in creating a Throwable generic class because it does not bring any benefit. The reason is that the information is only used for  code control compiler of programmers. In the  runtime of Java, Generic information does not exist, an object of Mistake <Account> or Mistake <User> are an object type of Mistake.
} catch( Mistake<Account> ea) {
    // If exceptions Mistake occurs, this block will be executed
    ...
} catch( Mistake<User> eu) {
     // This block is never executed
    ...
}

3- Generic Methods

A method in class or Interface may be generified. 
MyUtils.java
package org.o7planning.tutorial.generics.m;

import java.util.ArrayList;

import org.o7planning.tutorial.generics.ci.KeyValue;

public class MyUtils {


   // <K, V>: To say that this method has two parameters K, V
   // Method returns K.
   public static <K, V> K getKey(KeyValue<K, V> entry) {
       K key = entry.getKey();
       return key;
   }

 
   // <K, V>: To say that this method has two parameters K, V
   // Method returns V.    
   public static <K, V> V getValue(KeyValue<K, V> entry) {
       V value = entry.getValue();
       return value;
   }

 
   // ArrayList <E>: The list contains the elements of type E
   // Method returns the type E.    
   public static <E> E getFirstElement(ArrayList<E> list) {
       if (list == null || list.isEmpty()) {
           return null;
       }
       E first = list.get(0);
       return first;
   }

}
For example, using generics method :
MyUtilsDemo.java
package org.o7planning.tutorial.generics.m;

import java.util.ArrayList;

import org.o7planning.tutorial.generics.ci.KeyValue;

public class MyUtilsDemo {

   public static void main(String[] args) {

       // K = Integer: Phone
       // V = String: Name
       KeyValue<Integer, String> entry1 = new KeyValue<Integer, String>(12000111, "Tom");
       KeyValue<Integer, String> entry2 = new KeyValue<Integer, String>(12000112, "Jerry");

       // (K = Integer).
       Integer phone = MyUtils.getKey(entry1);
       System.out.println("Phone = " + phone);

       // A list containing the element type KeyValue<Integer, String>.
       ArrayList<KeyValue<Integer, String>> list = new ArrayList<KeyValue<Integer, String>>();

       // Add element to list
       list.add(entry1);
       list.add(entry2);

       KeyValue<Integer, String> firstEntry = MyUtils.getFirstElement(list);

       System.out.println("Value = " + firstEntry.getValue());
   }

}

4- Generic Object Initialization

Sometimes, you want to initialize a  Generic object:
// Generic Object Initialization

T t = new T(); // Error
If you want to initialize  Generic object,  you need to pass Class<T> object to Java which helps Java to create generic object at runtime by using Java Reflection.
Bar.class
package org.o7planning.tutorial.generics.o;

import java.util.Date;

public class Bar {

   // Class này phải có cấu tử mặc định
   public Bar() {

   }

   public void currentDate() {
       System.out.println("Now is: " + new Date());
   }

}
MyGeneric.java
package org.o7planning.tutorial.generics.o;

public class MyGeneric<T> {

   private T tobject;

   public MyGeneric(Class<T> tclass)
           throws InstantiationException, IllegalAccessException {
       
       this.tobject = (T) tclass.newInstance();
       
   }

   public T getTObject() {
       return this.tobject;
   }
}
MyGenericDemo.java
package org.o7planning.tutorial.generics.o;

public class MyGenericDemo {

   public static void main(String[] args) throws Exception {

       MyGeneric<Bar> mg = new MyGeneric<Bar>(Bar.class);

       Bar bar = mg.getTObject();

       bar.currentDate();
   }
}

5- Generic Array

You can declare a generic array, but you can not initialize a generic array.
// You can declare a generic array of generic.

T[] myarray;

// But you can not initialize a generic array.
// (This is not allowed).

T[] myarray = new T[5];  // Error!
Example:
GenericArray.java
package org.o7planning.tutorial.generics.a;

public class GenericArray<T> {

   private T[] array;

   // Contructor.
   public GenericArray(T[] array) {
       this.array = array;
   }

   public T[] getArray() {
       return array;
   }

   // Returns the last element of the array.
   public T getLastElement() {
       if (this.array == null || this.array.length == 0) {
           return null;
       }
       return this.array[this.array.length - 1];
   }

}
GenericArrayDemo.java
package org.o7planning.tutorial.generics.a;

public class GenericArrayDemo {

  public static void main(String[] args) {

   
      String[] names = new String[] { "Tom", "Jerry" };

      GenericArray<String> gArray = new GenericArray<String>(names);

      String last = gArray.getLastElement();
     
      System.out.println("Last Element = " + last);
  }

}
Returning to the question of why Java does not  support to  initialize a Generic array:
// Why Java don't support to initialize Generic array?

T[] genericArray = new T[10]; // Error!
The reason is that generic type does not exist at runtime, List<String> or List<Integer> are List. Generic only works with compilers to check code of programmers. It means that the compiler of Java need to know what is <T> to compile new T [10];. Without knowing it will consider T as a Object by default. Then:
// Suppose that Java allows to initialize  Generic array:

T[]  tarray = new T[10];


// At the time of compilation, the compiler will consider T as Object.
// This command is equivalent to.

T[] tarray  = new Object[10];

// At runtime of the application, if T is defined as String.
// It means that:

String[] tarray = new Object[10];

// The above is not allowed. Reason:
// Type mismatch: cannot convert from Object[] to String[]
If you want to initialize  Generic array,  you need to pass Class <T> object to Java which helps Java to create generic array at runtime by using Java Reflection. See examples:
GArray.java
package org.o7planning.tutorial.generics.a;

import java.lang.reflect.Array;

public class GArray<T> {

  private Class<T> tclass;

  private T[] myArray;

  public GArray(Class<T> tclass) {
      this.tclass = tclass;

      final int size = 10;
      myArray = (T[]) Array.newInstance(tclass, size);
  }

  public T[] getMyArray() {
      return this.myArray;
  }

}
GArrayDemo.java
package org.o7planning.tutorial.generics.a;

public class GArrayDemo {

   public static void main(String[] args) {

       GArray<Integer> garray = new GArray<Integer>(Integer.class);

       Integer[] myArray = garray.getMyArray();

       myArray[0] = 1;
       myArray[2] = 0;
   }

}

6- Generics with Wildcards

In generic code, the question mark (?), called the wildcard, represents an unknown type. A wildcard parameterized type is an instantiation of a generic type where at least one type argument is a wildcard. Examples of wildcard parameterized types are Collection<?>, List<? extends Number>, Comparator<? super String> and Pair<String,?>. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
Having wildcards at difference places have different meanings as well. e.g.
  • Collection<?> denotes all instantiations of the Collection interface regardless of the type argument.
  • List<? extends Number> denotes all list types where the element type is a subtype of Number.
  • Comparator<? super String> denotes all instantiations of the Comparator interface for type argument types that are supertypes of String.

A wildcard parameterized type is not a concrete type that could appear in a new expression. It just hints the rule enforced by java generics that which types are valid in any particular scenario where wild cards have been used.
  • Example:
Collection<?> coll = new ArrayList<String>();

// list type where the element type is a subtype of Number.
List<? extends Number> list = new ArrayList<Long>();

// A wildcard parameterized type.
Pair<String,?> pair = new Pair<String,Integer>();
Some of the invalid declarations. 
// String is not subclass of Number; so error
List<? extends Number> list = new ArrayList<String>();  

// String is not superclass of Integer
ArrayList<? super String> cmp = new ArrayList<Integer>();

6.1- Examples with wildcard

WildCardExample1.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;

public class WildCardExample1 {

   public static void main(String[] args) {

     
       // A list containing the elements of type String.
       ArrayList<String> listString = new ArrayList<String>();
       
       listString.add("Tom");
       listString.add("Jerry");

 
       // A list containing the elements of type Integer.
       ArrayList<Integer> listInteger = new ArrayList<Integer>();
       
       listInteger.add(100);

   
       // You can not declare:
       // ArrayList<Object> list = listString;        

       // A wildcard parameterized object
       ArrayList<? extends Object> list;

       // You can declare:
       list = listString;

       // Or
       list = listInteger;  
       
   }

}
WildCardExample2.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;
import java.util.List;

public class WildCardExample2 {

   public static void printElement(List<?> list) {
       for (Object e : list) {
           System.out.println(e);
       }
   }

   public static void main(String[] args) {

       List<String> names = new ArrayList<String>();
       names.add("Tom");
       names.add("Jerry");
       names.add("Donald");

       List<Integer> values = new ArrayList<Integer>();

       values.add(100);
       values.add(120);

       System.out.println("--- Names --");

       printElement(names);

       System.out.println("-- Values --");

       printElement(values);

   }

}

6.2- A wildcard parameterized type can not use generic methods

ValidWildcard1.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;

public class ValidWildcard1 {

   public static void main(String[] args) {

       // ArrayList<E>    
       // A list containing the elements of type String.
       ArrayList<String> listString
                           = new ArrayList<String>();

     
       // Using generic method: add(E)
       // Add not null element to list
       listString.add("Tom");

       listString.add("Jerry");


       // Add null element to list
       listString.add(null);
   }

}
InvalidWildcard1.java
package org.o7planning.tutorial.generics.w;

import java.util.ArrayList;

public class InvalidWildcard1 {

   public static void main(String[] args) {

       // ArrayList<E>    
       // A list containing the elements of type String.
       ArrayList<String> listString = new ArrayList<String>();

       // A wildcard parameterized type
       ArrayList<? extends Object> listWildcard = listString;

 
       // Using generic method: add(E)
       // A wildcard parameterized object can not be used
       // generic method with generic parameter not null.
       listWildcard.add("Tom"); // Error!

       listWildcard.add("Jerry"); // Error!
       
       // But can use generic methods
       // with null parameter.
       listWildcard.add(null);

   }

}

6.3- Wildcard can not participate in the new operator

A wildcard parameterized type is not a concrete type that could appear in a new expression. It just hints the rule enforced by java generics that which types are valid in any particular scenario where wild cards have been used.
// Wildcard không thể tham gia trong toán tử new
List<? extends Object> list= new ArrayList<? extends Object>();