Java Generics

Java Generics allows us to create a single class, interface, and method that can be used with different types of data (objects).

This helps us to reuse our code.

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


Java Generics Class

We can create a class that can be used with any type of data. Such a class is known as Generics Class.

Here's is how we can create a generics class in Java:

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());
  }
}

// create a generics class
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 Programming

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 used inside the angle bracket <> indicates the type parameter. Inside the Main class, we have created two objects of GenericsClass

  • intObj - Here, the type parameter T is replaced by Integer. Now, the GenericsClass works with integer data.
  • stringObj - Here, the type parameter T is replaced by String. Now, the GenericsClass works with string data.

Java Generics Method

Similar to the generics class, we can also create a method that can be used with any type of data. Such a class is known as Generics Method.

Here's is how we can create a generics method in Java:

Example: Create a Generics Method

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

    // initialize the class with Integer data
    DemoClass demo = new DemoClass();

    // generics method working with String
    demo.<String>genericsMethod("Java Programming");

    // generics method working with integer
    demo.<Integer>genericsMethod(25);
  }
}

class DemoClass {

  // creae a generics method
  public <T> void genericsMethod(T data) {
    System.out.println("Generics Method:");
    System.out.println("Data Passed: " + data);
  }
}

Output

Generics Method:
Data Passed: Java Programming
Generics Method:
Data Passed: 25

In the above example, we have created a generic method named genericsMethod.

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> and <Integer> inside the bracket before the method name.

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

demo.<Integer>genericMethod(25);

Note: We can call the generics method without including the type parameter. For example,

demo.genericsMethod("Java Programming");

In this case, the compiler can match the type parameter based on the value passed to the method.


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.

In the case of bound 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 class named GenericsClass. Notice the expression, notice the expression

<T extends Number> 

Here, GenericsClass is created with bounded type. This means GenericsClass 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. In this case, 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

With the help of generics in Java, we can 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 same 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. For example,

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

Here, we know that GenericsClass is working with Integer data only.

Now, if we try to pass data other than Integer to this class, the program will generate an error at compile time.

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.