Generics

Generics in Java is similar to templates in C++. In this article, we will go through Generics in Java.

Generics is the capability to parameterize types. With this capability, you can define a class or a method with generic types that can be substituted using concrete types by the compiler. The idea is to allow type (Integer, String, … etc, and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.

An entity such as class, interface, or method that operates on a parameterized type is called generic entity.

Why Generics?

The key benefit of generics is to enable Errors to be detected at Compile-time rather than at Run-time.

In Java, Object is the superclass of all other classes and Object reference can refer to any type object. These features lack type safety. Generics adds that type safety feature.

A generic class or method permits you to specify allowable types of objects that the class or method may work with. If you attempt to use the class or method with an incompatible object, a Compile-time error occurs.

  • Generics can help detect type errors at compile time, thus make programs more robust.
  • Generics can make programs easy to read.
  • Generics can avoid cumbersome castings.

What if we don’t use Generics?

Let’s look at this code. You will get a warning.

1
2
3
4
5
6
7
public class ShowUncheckedWarning {
public static void main(String[] args) {
//Raw Type is Unsafe
java.util.ArrayList list = new java.util.ArrayList();
list.add("Java Programming");
}
}

The compiler will report warning and generate a class file if no other errors in the program.

Warning message:

1
2
Note: ShowUncheckedWarning.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Here if we use ArrayList list = new ArrayList();, This is roughly equalivalent to:ArrayList<Object> list = new ArrayList<Object>();. Using raw Type is Unsafe and might cause Runtime error.

To fix the warning:

1
2
3
4
5
6
7
8
public class ShowUncheckedWarning {
public static void main(String[] args) {
java.util.ArrayList<String> list = new java.util.ArrayList<String>();
//or:
//java.util.ArrayList<String> list = new java.util.ArrayList<>();
list.add("Java Programming");
}
}

Create Generics

We use <> to specify parameter types in generic class.

Let’s see a minimum Generic class example from GeeksForGeeks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// A Simple Java program to show working of user defined 
// Generic classes

// We use < > to specify Parameter type
class Test<T>
{
// An object of type T is declared
T obj;
Test(T obj) { this.obj = obj; } // constructor
public T getObject() { return this.obj; }
}

// Driver class to test above
class Main
{
public static void main (String[] args)
{
// To create objects of generic class:
// BaseType <Type> obj = new BaseType <Type>()

// instance of Integer type
Test<Integer> iObj = new Test<Integer>(15);
System.out.println(iObj.getObject());

// instance of String type
Test<String> sObj = new Test<String>("GeeksForGeeks");
System.out.println(sObj.getObject());
}
}

Note:

  • In Parameter type we can not use primitives like ‘int’,‘char’ or ‘double’.
  • A generic class can have multiple type parameters.
  • Generic type information is present at compile time ONLY.
  • A generic class is shared by all its instances regardless of its actual generic type.
    • In above example, although Test<Integer> and Test<String> are two types, but there is only one class Test loaded into the JVM.

Type Parameter Naming Conventions

By convention, type parameter names are single, uppercase letters.

The most commonly used type parameter names are:

  • E - Element (used extensively by the Java Collections Framework)

  • K - Key

  • V - Value

  • N - Number

  • T - Type

  • S,U,V etc. - 2nd, 3rd, 4th types

Multiple Type Parameters

  • A generic class can have multiple type parameters.

For example, the generic OrderedPair class, which implements the generic Pair interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
interface Pair<K, V> {
public K getKey();
public V getValue();
}

class OrderedPair<K, V> implements Pair<K, V> {

private K key;
private V value;

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

public K getKey() { return key; }
public V getValue() { return value; }
}

// Driver class to test above
class Main
{
public static void main (String[] args)
{
// To create objects of generic class:
// BaseType <Type> obj = new BaseType <Type>()

// instance of String,Integer Pair
OrderedPair<String, Integer> p1 = new OrderedPair<>("Vines", 10);
System.out.println(p1.getKey());

// instance of String, String Pair
OrderedPair<String, String> p2 = new OrderedPair<>("Hello", "World!");
System.out.println(p2.getKey() + p2.getValue());

//You can also substitute a type parameter (i.e., K or V)
//with a parameterized type (i.e., List<String>). For example,
//OrderedPair<String, Box<Integer>> p3 =
// new OrderedPair<>("primes", Box<Integer>(10));

}
}

Because a Java compiler can infer the K and V types from the declaration OrderedPair<String, Integer>, these statements can be shortened using diamond notation <>.

Bounded Generic Type

There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter.

For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

To declare a bounded type parameter, list the type parameter’s name, followed by the extends keyword, followed by its upper bound.

Example:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(2, 2);
Circle circle = new Circle(2);
System.out.println("Same area? " + equalArea(rectangle, circle));
}

// Only accept GeometricObjects as parameter
public static < E extends GeometricObject > boolean equalArea(E object1, E object2) {
return object1.getArea() == object2.getArea();
}

Restrictions on Generics

Create an Instance

Cannot Create an Instance of a Generic Type. (i.e., new E()).

You cannot create an instance using a generic class type parameter.

1
E object = new E(); //Wrong

Array Creation

Generic Array Creation is Not Allowed. (i.e., new E[100]).

You cannot create an array using a generic class type parameter or a generic class.

1
2
3
4
5
6
7
8
9
//using generic type parameter
E[] elements = new E[100]; //Wrong

//using generic class
ArrayList<String>[] list=new ArrayList<String>[10]; //Wrong

//Allowed but compile warning
E[] elements = (E[])new Object[100];
ArrayList<String>[] list = (ArrayList<String>) new ArrayList[10];

Static Context

A Generic Type Parameter of a Class Is Not Allowed in a Static Context.

1
2
3
4
5
6
7
8
9
10
public class Test < E > {  // a Generic Class

public static void m(E o1) {} //Illegal

public static E o1; //Illegal

static {
E o2; //Illegal
}
}

To correctly use Generic in Static Context:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class GenericMethodDemo { // Not a Generic Class
public static void main(String[] args) {
Integer[] integers = {1, 2, 3, 4, 5};
String[] strings = {"London", "Paris", "New York","Austin"};

//To invoke a generic method,
//prefix the method name with the actual type in angle brackets.
GenericMethodDemo.<Integer>print(integers);
GenericMethodDemo.<String>print(strings);

//OR just simply invoke it as follows:
//GenericMethodDemo.print(integers);
//GenericMethodDemo.print(strings);
//print(integers);
//print(strings);
}

//Correct implementation of Generics
public static < E > void print(E[] list) {
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
System.out.println();
}

//Or without using Generics:
/*
public static void print(Object[] list) {
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
System.out.println();
}
*/
}

Exception Classes

Exception Classes Cannot be Generic.

1
public class MyException<T> extends Exception { } //Illegal

If it were allowed, you would have a catch clause for MyException<T> as follows:

1
2
3
4
5
6
try {
//...
}
catch (MyException<T> ex) {
//...
}

Example - Using Generics

Example : Using Generics to Create another Data Structure

MyStack.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.ArrayList;

public class MyStack<E> {

private ArrayList<E> list;

MyStack(){
list = new ArrayList<E>();
}

public int getSize(){
return list.size();
}

public E peek(){
return list.get(list.size()-1);
}

public E pop(){
E last = list.get(list.size()-1);
list.remove(list.size()-1);
return last;
}

public void push(E o){
list.add(o);
}

public boolean isEmpty(){
if(list.size() == 0){
return true;
}
return false;
}

@Override
public String toString(){
return "stack: " + list.toString();
}

}

MyQueue.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.LinkedList;

public class MyQueue<E> {

private LinkedList<E> list;

MyQueue(){
list = new LinkedList<E>();
}

public void enqueue(E e){
list.add(e);
}

public E dequeue(){
E dequeued = list.getFirst();
list.removeFirst();
return dequeued;
}

public int getSize(){
return list.size();
}

@Override
public String toString(){
return "Queue: " + list.toString();
}
}

TestStackQueue.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class TestStackQueue {
public static void main(String[] args) {
// Create a stack
MyStack < String > stack = new MyStack < > ();
// Add elements to the stack
stack.push("Tom"); // Push Tom to the stack
System.out.println("(1) " + stack);
stack.push("Susan"); // Push Susan to the the stack
System.out.println("(2) " + stack);
stack.push("Kim"); // Push it to the stack
stack.push("Michael"); // Push Michael to the stack
System.out.println("(3) " + stack);
// Remove elements from the stack
System.out.println("(4) " + stack.pop());
System.out.println("(5) " + stack.pop());
System.out.println("(6) " + stack);
// Create a queue
MyQueue < String > queue = new MyQueue < > ();
// Add elements to the queue
queue.enqueue("Tom"); // Add Tom to the queue
System.out.println("(7) " + queue);
queue.enqueue("Susan"); // Add Susan to the queue
System.out.println("(8) " + queue);
queue.enqueue("Kim"); // Add Kim to the queue
queue.enqueue("Michael"); // Add Michael to the queue
System.out.println("(9) " + queue);
// Remove elements from the queue
System.out.println("(10) " + queue.dequeue());
System.out.println("(11) " + queue.dequeue());
System.out.println("(12) " + queue);
}
}

Reference