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 anInteger
. - The
DoublePrinter
accepts aDouble
.
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 T
Code 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.