Generics in Java enable the creation of classes, interfaces, and methods that can operate with various data types while maintaining type safety.
Generics refer to parameterized types, which enable the specification of types during code usage rather than when it is defined.
Generics help in catching type errors at compile time, eliminating the need for casting, and enabling the implementation of generic algorithms.
A principal advantage of generic code is that it will automatically work with the type of data passed to its type parameter.
Many algorithms are logically the same, no matter what type of data they are applied to. For example, a Quicksort is the same whether it is sorting items of type Integer, String, Object, or Thread. With generics, you can define an algorithm once, independently of any specific type of data, and then apply that algorithm to a wide variety of data types without any additional effort.
The following are the advantages of generics.
Generics enable stronger type checks at compile time and can be used to develop robust and reusable code components.
Let’s illustrate this with a Printer
class designed to display text and numbers using different approaches – first without generics and then with generics.
Using a traditional approach, you have to create 2 classes since we have 2 different types of data, i.e., Number(Integer) and Text(String).
Create a class NumberPrinter for Printing Numbers
public class NumberPrinter {
private final Integer value;
public NumberPrinter(Integer value) {
this.value = value;
}
public void printData() {
System.out.println("Printing: " + value);
}
}
Create a class TextPrinter for Printing Text Data
public class TextPrinter {
private final String value;
public TextPrinter(String value) {
this.value = value;
}
public void printData() {
System.out.println("Printing: " + value);
}
}
Creating a Driver Class, i.e., Printer
public class Printer {
public static void main(String[] args) {
NumberPrinter numberObj = new NumberPrinter(10);
numberObj.printData(); // output = print: 10
TextPrinter textObj = new TextPrinter("Learning Generics");
textObj.printData(); // output = print: Learning Generics
}
}
Here, you can see, there is a code duplication due to only a change in the data types(Integer and String). So, here the data type is only the difference!
So, let’s understand the concept of Generics to avoid code duplication.
Let’s implement the above example using a Generic Printer class.
public class Printer<T> {
private final T value;
public Printer(T value) {
this.value = value;
}
public void printData() {
System.out.println("Printing: " + value);
}
}
To use this Printer class for different types, you simply instantiate it with the desired data type.
Printer<Integer> integerPrinter = new Printer<>(15);
integerPrinter.printData(); // output = print: 15
Printer<String> stringPrinter = new Printer<>("Learning Generics");
stringPrinter.printData(); // output = print: Learning Generics
Here, only one class (Printer) is required to print different data types.
Printer<T>: Here, T is the name of a type parameter contained within the angle bracket (< >) that can be used to declare a type as a common standard.
This name is used as a placeholder for the actual type that will be passed to the Printer when an object is created.
We can create the Printer objects for other data types, also like Double/Long. So, the Code reusability is achieved in style.
Printer(T value): This will accept a value of type T
(any type). Whatever type is specified for an instance of Printer.
package genericclass;
public class Box<T> {
private T t; // T stands for "Type"
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
System.out.println("Box contains: " + integerBox.get());
Box<String> stringBox = new Box<>();
stringBox.set("Hello World");
System.out.println("Box contains: " + stringBox.get());
}
}
Box contains: 10
Box contains: Hello World
Explanation: The Box
class is a generic container for different types of objects. By specifying the type parameter <T>,
It can safely hold any type, ensuring type safety and reducing the need for casts.
package genericclass;
public class ArrayPrinter<T> {
public void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
ArrayPrinter<String> stringPrinter = new ArrayPrinter<>();
stringPrinter.printArray(new String[]{"Hello", "World"});
ArrayPrinter<Integer> integerPrinter = new ArrayPrinter<>();
integerPrinter.printArray(new Integer[]{1, 2, 3, 4});
}
}
Hello World
1 2 3 4
Generic classes can also accept multiple types, as demonstrated in the example where they can accept both Integer and String.
The following example accepts both Integer and String.
public class MultiPrinter<T, V> {
private final T value1;
private final V value2;
public MultiPrinter(T value1, V value2) {
this.value1 = value1;
this.value2 = value2;
}
public void printValue() {
System.out.println("print: " +value1 + " : " + value2);
}
}
//Using this class
MultiPrinter<Integer, String> multiPrinter = new MultiPrinter<>(15, "Hi");
multiPrinter.printValue(); // output = print: 15 : Hi
When declaring an instance of a generic type, the type argument passed to the type parameter must be a reference type.
You cannot use a primitive type, such as int or char.
Printer<int> intOb = new Printer<int>(53);
// Error, can't use primitive type
You must be logged in to submit a review.