Introduction
Generics in Java allow writing flexible and reusable code by providing an additional layer of abstraction. They allow creating a single algorithm for multiple types of objects. Generics provide type safety and avoid runtime errors. In this lab, we will understand various aspects of Generics in Java.
Creating a Generic Class
We can create a Generic class in Java with the help of the diamond operator (<>). In the following code block, we will create a Generic class named MyGenericClass that can accept any type of object. We will also create an object of type MyGenericClass with integer and string types as parameters.
// ~/project/MyGenericClass.java
class MyGenericClass<T> {
T field1;
public MyGenericClass(T field1) {
this.field1 = field1;
}
public T getField1() {
return field1;
}
}
public class Main {
public static void main(String[] args) {
MyGenericClass<Integer> myIntObj = new MyGenericClass<Integer>(100);
MyGenericClass<String> myStringObj = new MyGenericClass<String>("Hello World");
System.out.println(myIntObj.getField1());
System.out.println(myStringObj.getField1());
}
}
To run the code, open the terminal and navigate to the ~/project directory. Compile the code with the following command:
javac MyGenericClass.java
After successful compilation, run the code with the following command:
java Main
The output of the code will be:
100
Hello World
Creating a Generic Method
We can also create a Generic method in Java. The Generic method can work with different types of objects. In the following code block, we will create a Generic method named printArray that can print any type of array.
// ~/project/Main.java
public class Main {
public static <T> void printArray(T[] array) {
for (T element : array)
System.out.println(element);
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
String[] stringArray = {"Hello", "World"};
printArray(intArray);
printArray(doubleArray);
printArray(stringArray);
}
}
To run the code, open the terminal and navigate to the ~/project directory. Compile the code with the following command:
javac Main.java
After successful compilation, run the code with the following command:
java Main
The output of the code will be:
1
2
3
4
5
1.1
2.2
3.3
4.4
Hello
World
Creating a Bounded Generic Method
We can also create a Bounded Generic method. Bounded Generics limit the range of types accepted by the method. We use the extends keyword to impose the bound. In the following code block, we will create a Bounded Generic method named printNumbers that can print the numbers of objects of type Number and its subclasses.
// ~/project/Main.java
public class Main {
public static <T extends Number> void printNumbers(T[] numbers) {
for (T number : numbers)
System.out.println(number);
}
public static void main(String[] args) {
Integer[] intNumbers = {1, 2, 3, 4, 5};
Double[] doubleNumbers = {1.1, 2.2, 3.3, 4.4};
printNumbers(intNumbers);
printNumbers(doubleNumbers);
}
}
To run the code, open the terminal and navigate to the ~/project directory. Compile the code with the following command:
javac Main.java
After successful compilation, run the code with the following command:
java Main
The output of the code will be:
1
2
3
4
5
1.1
2.2
3.3
4.4
Understanding Type Safety in Generics
Generics provide type safety, ensuring that we define only one type of object to work with at a time. In the following code block, we will create an ArrayList of integers without defining its type parameter. It is a bad practice to use Generics without type parameters. The code will compile, but it will return a runtime error when we try to access its elements of other types.
// ~/project/Main.java
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList integerList = new ArrayList();
integerList.add(1);
integerList.add(2);
integerList.add(3);
integerList.add("Hello World");
for (Object element : integerList) {
System.out.println((Integer)element);
}
}
}
To run the code, open the terminal and navigate to the ~/project directory. Compile the code with the following command:
javac Main.java
After successful compilation, run the code with the following command:
java Main
The output of the code will be:
1
2
3
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.lang.Integer
at Main.main(Main.java:13)
We get a runtime error because the integerList contains elements of the Integer type and String type. This is because we did not define the type parameter while creating the integerList.
Understanding Type Erasure in Generics
Java uses Type Erasure in Generics to ensure that no additional overhead is needed at runtime. The compiler replaces the type parameter with the Object class or the parent class (in case of bounded Generics) during the process of type erasure. In the following code block, we will see how Generics works with primitive types at compile time and runtime.
// ~/project/Main.java
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
int sum = 0;
for (int i = 0; i < integerList.size(); i++) {
sum += integerList.get(i);
}
System.out.println(sum);
}
}
To run the code, open the terminal and navigate to the ~/project directory. Compile the code with the following command:
javac Main.java
After successful compilation, run the code with the following command:
java Main
The output of the code will be:
6
In the above code block, we tried to add the elements of the integerList. As we know, primitive types like int cannot be used with Generics. Due to type erasure, the type parameter Integer is replaced with the Object class at compile time. However, autoboxing and unboxing allowed us to perform the addition of integers at compile time. At runtime, the code performed like normal code execution, without any additional overhead.
Summary
In this lab, we learned about Java Generics. We covered the basic concepts related to Generic classes, Generic methods, Bounded Generic methods, Type Safety in Generics, and Type Erasure in Generics. We also saw how to write flexible and reusable code using Generics. We hope this lab helped you understand Generics in Java.



