Join our newsletter for the latest updates.
Java Generics

Java Generics

In this tutorial, we will learn about Java Generics, how to create generics class and methods and its advantages with the help of examples.

In Java, Generics helps to create classes, interfaces, and methods that can be used with different types of objects (data). Hence, allows us to reuse our code.

Note: Generics does not work with primitive types (int, float, char, etc).


Working of Generics

To understand how Generics is used in Java, we can use the ArrayList class of the Java collections framework.

The ArrayList class is an example of a generics class. We can use ArrayList to store data of any type. For example,

import java.util.ArrayList;

class Main {
   public static void main(String[] args) {

      // create an array list to store Integer data
      ArrayList<Integer> list1 = new ArrayList<>();
      list1.add(4);
      list1.add(5);
      System.out.println("ArrayList of Integer: " + list1);

      // creates an array list to store String data
      ArrayList<String> list2 = new ArrayList<>();
      list2.add("Four");
      list2.add("Five");
      System.out.println("ArrayList of String: " + list2);

      // creates an array list to store Double data
      ArrayList<Double> list3 = new ArrayList<>();
      list3.add(4.5);
      list3.add(6.5);
      System.out.println("ArrayList of Double: " + list3);
   }
}

Output

ArrayList of Integer: [4, 5]
ArrayList of String: [Four, Five]
ArrayList of Double:  [4.5, 6.5]

In the above example, we have used the same ArrayList class to store elements of Integer, String, and Double types. This is possible because of Java Generics.

Here, notice the line,

ArrayList<Integer> list1 = new ArrayList<>();

We have used Integer inside the angle brackets, <>. The angle bracket, <> is known as the type parameter in generics.

The type parameter is used to specify the type of objects (data) that the generics class or method works on.


Create Generics Class

Now that we know how generics work in Java, let's see how we can create our own generics class.

Example: Create a Generics Class

class Main {
  public static void main(String[] args) {

    // initialize generic class with Integer data
    GenericsClass<Integer> intObj = new GenericsClass<>(5);
    System.out.println("Generic Class returns: " + intObj.getData());

    // initialize generic class with String data
    GenericsClass<String> stringObj = new GenericsClass<>("Java Programming");
    System.out.println("Generic Class returns: " + stringObj.getData());
  }
}

class GenericsClass<T> {

  // variable of T type
  private T data;

  public GenericsClass(T data) {
    this.data = data;
  }

  // method that return T type variable
  public T getData() {
    return this.data;
  }
}

Output

Generic Class returns: 5
Generic Class returns: Java Programing

In the above example, we have created a generic class named GenericsClass. This class can be used to work with any type of data.

class GenericsClass<T> {...}

Here, T indicates the type parameter. Inside the Main class, we have created objects of GenericsClass named intObj and stringObj.

  • While creating intObj, the type parameter T is replaced by Integer. This means intObj uses the GenericsClass to work with integer data.
  • While creating stringObj, the type parameter T is replaced by String. This means stringObj uses the GenericsClass to work with string data.

Create Generics Methods

Similar to the generics class, we can also create our own generics methods in Java.

Example: Create a Generic Method

class Main {
  public static void main(String[] args) {

    // initialize the class with Integer data
    DemoClass demo = new DemoClass();
    demo.<String>genericsMethod("Java Programming");
  }
}

class DemoClass {

  // generics method
  public <T> void genericsMethod(T data) {
    System.out.println("This is a generics method.");
    System.out.println("The data passed to method is " + data);
  }
}

Output

This is a generics method.
The data passed to the method: Java Programming

In the above example, we have created a generic method named genericsMethod inside an ordinary class.

public <T> void genericMethod(T data) {...}

Here, the type parameter <T> is inserted after the modifier (public) and before the return type (void).

We can call the generics method by placing the actual type <String> inside the bracket before the method name.

demo.<String>genericMethod("Java Programming");

Note: In most cases, we can omit the type parameter while calling the generics method. It is because the compiler can match the type parameter using the value passed to the method. For example,

demo.genericsMethod("Java Programming");

Bounded Types

In general, the type parameter can accept any data types (except primitive types). However, if we want to use generics for some specific types (such as accept data of number types) only, then we can use bounded types.

We use the extends keyword. For example,

<T extends A>

This means T can only accept data that are subtypes of A.

Example: Bounded Types

class GenericsClass <T extends Number> {

  public void display() {
    System.out.println("This is a bounded type generics class.");
  }
}

class Main {
  public static void main(String[] args) {

    // create an object of GenericsClass
    GenericsClass<String> obj = new GenericsClass<>();
  }
}

In the above example, we have created a generics class with bounded type. Here, notice the expression,

<T extends Number> 

This means T can only work with data types that are children of Number (Integer, Double, and so on).

However, we have created an object of the generics class with String. This is why when we run the program, we will get the following error.

GenericsClass<String> obj = new GenericsClass<>();
                                                 ^
    reason: inference variable T has incompatible bounds
      equality constraints: String
      lower bounds: Number
  where T is a type-variable:
    T extends Number declared in class GenericsClass

Advantages of Java Generics

1. Code Reusability

Generics allow us to write code that will work with different types of data. For example,

public <T> void genericsMethod(T data) {...}

Here, we have created a generics method. This method can be used to perform operations on integer data, string data and so on.

2. Compile-time Type Checking

The type parameter of generics provides information about the type of data used in the generics code.

Hence, any error can be identified at compile time which is easier to fix than runtime errors. For example,

// without using Generics
NormalClass list = new NormalClass();

// calls method of NormalClass
list.display("String");

In the above code, we have a normal class. We call the method named display() of this class by passing a string data.

Here, the compiler does not know if the value passed in the argument is correct or not. However, let's see what will happen if we use the generics class instead.

// using Generics
GenericsClass<Integer> list = new GenericsClass<>();

// calls method of GenericsClass
list2.display("String");

In the above code, we have a generics class. Here, the type parameter indicates that the class is working on Integer data.

Hence when the string data is passed in argument, the compiler will generate an error.

3. Used with Collections

The collections framework uses the concept of generics in Java. For example,

// creating a string type ArrayList
ArrayList<String> list1 = new ArrayList<>();

// creating a integer type ArrayList
ArrayList<Integer> list2 = new ArrayList<>();

In the above example, we have used the same ArrayList class to work with different types of data.

Similar to ArrayList, other collections (LinkedList, Queue, Maps, and so on) are also generic in Java.