Java Generics

Summary: In this tutorial, you will learn about Java generics and how to use it effectively.

Introduction to Java Generics

Let’s take a scenario to understand why you need Java generics.

Suppose you want to define a class that takes an Integer and displays it on the screen. You may come up with the following IntegerPrinter class:

public class IntegerPrinter {
    private final Integer value;

    public IntegerPrinter(Integer value) {
        this.value = value;
    }

    public void print() {
        System.out.println(value);
    }
}Code language: Java (java)

The IntegerPrinter has a constructor that accepts an Integer. It includes the print() method that prints out the Integer value.

Later, you get a new requirement to define a class that takes a Double and displays it on the screen.

Since the IntegerClass cannot cover the new requirement, you may define a separate class called DoublePrinter:

public class DoublePrinter {
    private final Double value;

    public DoublePrinter(Double value) {
        this.value = value;
    }

    public void print() {
        System.out.println(value);
    }
}Code language: Java (java)

The DoublePrinter class has the same logic as the IntegerPrinter class except that the types are different:

  • The IntegerPrinter accepts an Integer.
  • The DoublePrinter accepts a Double.

Also, if you want to want to cover over types such as Short, Long, and Float, you need to define a class for each. This is kind of redundant.

Java generics address this redundancy by introducing generic types. A generic type is a generic class that is parametrized over types. It means that you can define a single class that can cover both Integer and Double types.

Java generics example

For example, the following defines a generic Printer class:

public class Printer<T> {
    private final T value;

    public Printer(T value) {
        this.value = value;
    }

    public void print() {
        System.out.println(value);
    }
}Code language: Java (java)

How it works.

First, specify the type parameter inside angle brackets <> followed by the class name:

public class Printer<T> {
   //..
}Code language: Java (java)

The type parameter T is also called a type variable. Once you define the type parameter, you can use it anywhere inside the class.

Note that the type parameter T can be any non-primitive type. In other words, it can be any class or interface type.

Second, replace a specific type e.g., Integer or Double inside the class with the type variable T.

For example, instead of using the Integer type, we use T as the type for the value field:

private final T value;Code language: Java (java)

Also, in the constructor, we use the type parameter T for the value parameter:

public Printer(T value) {
   this.value = value;
}Code language: Java (java)

Type parameter naming conventions

By convention, the names of type parameters are single uppercase letters. Here are the commonly used type parameter names:

  • T – Type
  • K – Key
  • V – Value
  • E – Element (used by the Java Collections Framework)
  • S, U, .. 2nd 3rd types

Using Java generic types

To use a generic type, you need to perform a generic type invocation. To do that, you replace the type parameter (T) with a concrete type.

For example, The following use the Printer generic type that works with Integer type:

Printer<Integer> printer;Code language: Java (java)

The generic type invocation is similar to a method invocation. However, instead of passing an argument to a method, you pass a type argument (Integer in this case) to the Printer class.

This declaration declares that the printer holds a reference to a Printer of Integer:

Printer<Integer> printer;Code language: Java (java)

It doesn’t create a new instance of the Printer class.

To create a new instance of Printer<Integer> class, you use the new keyword and place <Integer> between the class name and the parenthesis:

Printer<Integer> printer = new Printer<Integer>(10);Code language: Java (java)

Since the Java compiler can infer the type Printer<Integer> by looking at the constructor, you can use the var keyword:

var printer = new Printer<Integer>(10);Code language: Java (java)

The var keyword makes your code more concise and readable.

If you want to explicitly specify that the printer is a reference to the Printer<Interger> type, you can use an empty set of type arguments in the constructor:

Printer<Integer> printer = new Printer<>(10);Code language: Java (java)

The pair of angle brackets <> is informally called the diamond.

After having an instance of the Printer<Integer> class, you can call its method like this:

var printer = new Printer<Integer>(10);
printer.print();Code language: Java (java)

If you want to create a new Printer of Double, you can replace the Integer type with the Double type like this:

var printer = new Printer<Double>(10.0);
printer.print();Code language: Java (java)

And it’ll work as expected.

So by utilizing the generic type, you can write a more generic code that has the same logic but covers multiple types.

Bounded type parameters

Suppose you want to restrict the type T of the Printer<T> class to work with Number and its subclasses only. To do that you can use bounded type parameters.

To declare a bounded type parameter, you specify the type parameter’s name, followed by the extends keyword, and followed by its upper bound:

public classs MyClass<T extends Type1> {
   // ...
}Code language: PHP (php)

To specify multiple bounds, you use the & to separate the bounds:

public classs MyClass<T extends Type1 & Type2 & Type3> {
   // ...
}Code language: PHP (php)

In this syntax, the T is a subtype of all the types listed in the bound including Type1, Type2, and Type3.

Note that if one of the bounds is a class, you need to specify it first like this:

public classs MyClass<T extends MyClass1 & MyInterface1 & MyInterface2> {
   // ...
}Code language: PHP (php)

The following example illustrates how to use the Printer class with the type parameter as a subclass of the Number class:

public class Printer<T extends Number> {
    private final T value;

    public Printer(T value) {
        this.value = value;
    }

    public void print() {
        System.out.println(value);
    }
}Code language: PHP (php)

Now, you can only create a new instance of the Printer of any class that is the Number class or its subclass. For example:

var printer = new Printer<Float>(10.5f);
printer.print();Code language: PHP (php)

Because T extends Number, the value is an instance of the Number class. Therefore, you can access the fields and methods of the Number class via the value reference.

For example, you can define an intValue() method of the Number class that returns the integer value of the value field:

public class Printer<T extends Number> {
    private final T value;


    public Printer(T value) {
        this.value = value;
    }

    public void print() {
        System.out.println(value);
    }

    public int intValue() {
        return this.value.intValue();
    }
}Code language: PHP (php)

Here’s the program that uses the intValue() method:

public class App {
    public static void main(String[] args) {
        var printer = new Printer<Float>(10.5f);
        printer.print();
        System.out.println(printer.intValue());
    }
}Code language: PHP (php)

Also, if you use a class that is not the subclass of the Number class in the generic type invocation, you’ll get an error.

For example, the following example attempts to declare a reference to the Printer of a String and causes an error:

Printer<String> printer;Code language: HTML, XML (xml)

Error:

java: type argument java.lang.String is not within bounds of type-variable TCode language: CSS (css)

Summary

  • In Java, a generic type is a generic class that is parametrized over types.
  • Use generic types to make your code more flexible, which can cover various types.
  • Use the extends keyword to define a bounded type parameter.