When and Why to Use Generics in Java
When and Why to use Generics in Java
What are Generics ?
Generics in Java let you write classes, interfaces and methods that can operate on different data types, while still providing type safety.
Syntax of Generics
You use type parameters like (T, E, K, V) inside angle brackets
Ex 1: Generic Class
A generic class is a class that can work with different types of data without having to write a new class for each type. You give it a "placeholder" for a type, and when you create an instance of the class, you tell which actual type to use. This makes the code reusable and type-safe
public class Main {
private static record Apple(String color, String type){};
private static record Orange(String country){};
private static class Fruitbox<T>{
private final T[] fruits;
public Fruitbox(T[] fruits){
this.fruits = fruits;
};
public void countAndPrintSize(){
System.out.println(String.format("Size of the box is: %d",fruits.length));
};
}
public static void main(String[] args){
final var apple1 = new Apple("","");
final var apple2 = new Apple("","");
final var orange1 = new Orange("");
final var apples = new Apple[]{apple1, apple2};
final var oranges = new Orange[]{orange1};
final var appleBox = new Fruitbox<Apple>(apples);
final var orangeBox = new Fruitbox<Orange>(oranges);
appleBox.countAndPrintSize();
orangeBox.countAndPrintSize();
}
}
Ex 2: Generic Methods
public class Main {
private static class Utility {
public <T> void printArray(T[] arr){
for(T element: arr){
System.out.println(element);
}
}
}
public static void main(String[] args){
final var utility = new Utility();
final var arr = new Integer[]{3,6,7};
String[] cities = {"Berlin", "Hamburg"};
utility.printArray(arr);
utility.printArray(cities);
}
}
Why <T> is required
If you removed <T>:
public void printArray(T[] arr) { // Compiler error
The compiler would say:
Cannot resolve symbol
T
Because Java needs to know where T is declared. <T>is that decleration.
Ex: 3 Bounded type Parameters
You can restrcit what types a generic accepts using bounds.
Wildcard Uppert bound
private static class Utility {
public <T> void printArray(T[] arr){
for(T element: arr){
System.out.println(element);
}
}
public void printNumbers(List<? extends Number> arr){
for(Number i: arr){
System.out.println(i.intValue()*2);
}
}
}
Wildcars using LowerBound
private static class Utility {
public <T> void printArray(T[] arr){
for(T element: arr){
System.out.println(element);
}
}
public void printNumbers(List<? super Integer> arr){
for(Number i: arr){
System.out.println(i.intValue()*2);
}
}
}
List<? super Integer> means:
- The list can be of
Integer - OR any superclass of
Integer(Number,Object)
Double Generic
private static class Cache<T>{
private String keyObj;
private T valueObj;
private Map<String, T> storage = new HashMap<String, T>();
public void put(String key, T value) {
storage.put(key, value);
}
public T get(String key) {
return storage.get(key);
}
}
The whole sample code below;
public class Main {
static private record Apple(String color, String type){};
static private record Orange(String country){};
private static class Utility {
public <T> void printArray(T[] arr){
for(T element: arr){
System.out.println(element);
}
}
public void printNumbers(List<? extends Number> arr){
for(Number i: arr){
System.out.println(i.intValue()*2);
}
}
}
private static class Cache<T>{
private String keyObj;
private T valueObj;
private Map<String, T> storage = new HashMap<String, T>();
public void put(String key, T value) {
storage.put(key, value);
}
public T get(String key) {
return storage.get(key);
}
}
public static void main(String[] args){
final var utility = new Utility();
final var arr = new Integer[]{3,6,7};
String[] cities = {"Berlin", "Hamburg"};
utility.printArray(arr);
utility.printArray(cities);
List<Number> ages = List.of(23, 4, 5);
utility.printNumbers(ages);
Cache<Apple> appleCache = new Cache<Apple>();
Apple apple1 = new Apple("","");
appleCache.put("",apple1);
}
}
- Playground : https://leetcode.com/playground/2ymAjk6A