Skip to main content
Java

Introduction to Java Generics

4 mins

A toolbox containing interchangeable tools (e.g., different types of screwdrivers or wrenches), representing the flexibility and reusability provided by generics in handling different data types

What is Java Generics #

Java Generics is a feature introduced in Java 5 that allows developers to write and use generic classes and methods. In simpler terms, Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces, and methods, much like the way we pass values as parameters to methods. This feature brings stronger type checking at compile-time and eliminates the risk of ClassCastException that was common when working with collections before Java 5.

Before Generics #

Pre-generics, Java used Object types in collections.

Before Generics, we had to use collections that could hold any type of object. For example, prior to Java 5, a collection map could hold any type of object. This meant that we had to cast the object to the type we wanted when we retrieved it from the collection. This was not type-safe and could lead to runtime errors.

import java.util.HashMap;
import java.util.Map;

public class OldStyleMapExample {
    public static void main(String[] args) {
        // Creating a map to store String keys and Integer values
        Map map = new HashMap();

        // Adding elements to the map
        map.put("one", new Integer(1));
        map.put("two", new Integer(2));
        map.put("three", new Integer(3));

        // Retrieving and using values from the map
        Integer number = (Integer) map.get("two");

        if (number != null) {
            int value = number.intValue();
            System.out.println("Value for 'two': " + value);
        }

        // Adding an element with incorrect type (no compile-time error)
        map.put("four", "four");

        // Attempting to retrieve the element and cast it to Integer
        // This will throw a ClassCastException at runtime
        Integer wrongType = (Integer) map.get("four");
    }
}

Using Generics #

Post-generics, Java uses Generics to specify the type of objects that a collection can hold.

With Generics, we can specify the type of objects that a collection can hold. This makes the code more readable and type-safe. The compiler can now detect type mismatches at compile-time, and we no longer need to cast the objects we retrieve from the collection.

Instead of declaring a map as

Map map = new HashMap();

You would declare the type parameters for the map, that is specifying the classes or interfaces that the map can hold. For example, to create a map that can hold String keys and Integer values, you would declare the map as.

Map<String, Integer> map = new HashMap<>();

So only keys of type String and values of type Integer can be added to the map. If you try to add an element of a different type, the compiler will give you a compile-time error, as illustrated in the following example.

import java.util.HashMap;
import java.util.Map;

public class GenericsMapExample {
    public static void main(String[] args) {
        // Creating a map to store String keys and Integer values
        Map<String, Integer> map = new HashMap<>();

        // Adding elements to the map
        map.put("one", 1);
        map.put("two", 2);
        map.put("three", 3);

        // Retrieving and using values from the map
        Integer number = map.get("two");

        if (number != null) {
            int value = number;
            System.out.println("Value for 'two': " + value);
        }

        // Adding an element with incorrect type (compile-time error)
        // This will not compile
        map.put("four", "four");

        // Attempting to retrieve the element and cast it to Integer
        // This will not compile
        Integer wrongType = map.get("four");
    }
}

Generics protects us from runtime errors.

This is great advantage of Generics, as it protects us from runtime errors by providing compile-time type checking. When the codebase is large, it is difficult to find the source of runtime errors. Generics help us to find the source of errors at compile time.

Type Erasure #

Generics are a compile-time feature.

Generics are a compile-time feature, meaning that the type information is only available at compile-time. At runtime, the type information is erased, and the JVM only sees the raw types. This is known as type erasure.

At compile time the code is

List<String> list = new ArrayList<>();

At runtime, the code is

List list = new ArrayList();

This makes the generic code backward compatible with older versions of Java.