Java Generics allow classes, interfaces, and methods to work with different data types while providing compile-time type safety. Generics reduce the need for explicit type casting and help prevent type-related errors.
Without generics, a collection such as ArrayList can store different kinds of objects when used as a raw type. This can lead to errors when retrieving values.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("Rahul");
list.add(100);
String name = (String) list.get(0);
System.out.println(name);
}
}
This code uses a raw ArrayList. It allows different types of objects and requires casting when retrieving values.
Using generics makes the expected type clear:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("Rahul");
names.add("Amit");
System.out.println(names.get(0));
}
}
Output:
Rahul
You can specify the type of elements a collection should
store by using angle brackets <>.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
ArrayList<Integer> numbers = new ArrayList<>();
ArrayList<Double> prices = new ArrayList<>();
names.add("Priya");
numbers.add(50);
prices.add(99.50);
System.out.println(names);
System.out.println(numbers);
System.out.println(prices);
}
}
Output:
[Priya]
[50]
[99.5]
For example, ArrayList<String> accepts
String values but does not accept an Integer value.
This type restriction is checked by the compiler.
A generic class uses a type parameter, commonly written
as T. The type is supplied when an object
is created.
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
Box<String> box = new Box<>();
box.setValue("Hello Java");
System.out.println(box.getValue());
Box<Integer> numberBox = new Box<>();
numberBox.setValue(100);
System.out.println(numberBox.getValue());
}
}
Output:
Hello Java
100
The same Box class works with different reference types without needing separate classes for each type.
The letter T is a conventional name for a
generic type parameter. It is not a reserved Java keyword.
class Display<T> {
T data;
Display(T data) {
this.data = data;
}
void show() {
System.out.println(data);
}
}
public class Main {
public static void main(String[] args) {
Display<String> d1 = new Display<>("Java");
Display<Integer> d2 = new Display<>(25);
d1.show();
d2.show();
}
}
Output:
Java
25
Common generic parameter naming conventions:
| Letter | Common meaning |
|---|---|
| T | Type |
| E | Element |
| K | Key |
| V | Value |
| N | Number |
A generic class can accept more than one type parameter. For example, a class can store a key and a value.
class Pair<K, V> {
private K key;
private V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
void display() {
System.out.println("Key: " + key);
System.out.println("Value: " + value);
}
}
public class Main {
public static void main(String[] args) {
Pair<Integer, String> student =
new Pair<>(101, "Rahul");
student.display();
}
}
Output:
Key: 101
Value: Rahul
Here, K represents the key type and
V represents the value type.
A generic method declares its own type parameter before the return type. It can be used independently of whether its containing class is generic.
public class Main {
public static <T> void display(T value) {
System.out.println(value);
}
public static void main(String[] args) {
display("Hello");
display(100);
display(25.5);
}
}
Output:
Hello
100
25.5
Java can usually infer the type argument from the value passed to the method.
A generic method can return a value of its type parameter.
public class Main {
public static <T> T getValue(T value) {
return value;
}
public static void main(String[] args) {
String name = getValue("Rahul");
Integer number = getValue(50);
System.out.println(name);
System.out.println(number);
}
}
Output:
Rahul
50
The return type T matches the inferred
type of the argument in each call.
A bounded type parameter restricts which types can be
used. For example, T extends Number allows
Number and its subclasses, such as Integer and Double.
class Calculator<T extends Number> {
private T number;
Calculator(T number) {
this.number = number;
}
double getDoubleValue() {
return number.doubleValue();
}
}
public class Main {
public static void main(String[] args) {
Calculator<Integer> c1 = new Calculator<>(100);
Calculator<Double> c2 = new Calculator<>(25.5);
System.out.println(c1.getDoubleValue());
System.out.println(c2.getDoubleValue());
}
}
Output:
100.0
25.5
A type such as String cannot be used with this
Calculator class because String does not
extend Number.
A type parameter can have more than one bound. When using a class and interfaces, the class bound must appear first.
class Data<T extends Number & Comparable<T>> {
private T value;
Data(T value) {
this.value = value;
}
T getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
Data<Integer> data = new Data<>(50);
System.out.println(data.getValue());
}
}
Output:
50
Here, T must extend Number and implement Comparable for the same type. Integer satisfies both requirements.
The wildcard symbol ? represents an
unknown type. It is useful when a method should accept
collections of different element types.
import java.util.Arrays;
import java.util.List;
public class Main {
public static void display(List<?> items) {
for (Object item : items) {
System.out.println(item);
}
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Rahul", "Amit");
List<Integer> numbers = Arrays.asList(10, 20, 30);
display(names);
display(numbers);
}
}
Output:
Rahul
Amit
10
20
30
List<?> can accept a List with any
element type. Reading an element from it gives a value
that can safely be treated as an Object.
<? extends Number> accepts a
collection whose element type is Number or a subtype
of Number.
import java.util.List;
public class Main {
public static double total(List<? extends Number> numbers) {
double sum = 0;
for (Number number : numbers) {
sum += number.doubleValue();
}
return sum;
}
}
<? super Integer> accepts a
collection whose element type is Integer or a
supertype of Integer, such as Number or Object.
import java.util.List;
public class Main {
public static void addNumbers(List<? super Integer> numbers) {
numbers.add(10);
numbers.add(20);
}
}
extends when you mainly
need to read values as a given upper-bound type.
Use super when you need to add values of
the specified lower-bound type.
new T().new T[10].For example, use this:
ArrayList<Integer> numbers = new ArrayList<>();
Instead of trying to use a primitive type argument:
// Invalid Java:
ArrayList<int> numbers = new ArrayList<>();
1. What is the main purpose of Java Generics?
2. Which type parameter is commonly used to represent a generic type?
3. Which declaration is valid Java?