Java Generics


Generics are one of the most powerful features in Java. They allow you to write flexible, reusable, and type-safe code. In simple terms, Generics mean writing classes, interfaces, and methods that can work with different data types without rewriting the same logic multiple times.

You’ll often see angle brackets like <T> in Java code. That’s where generics come into play. Let’s break it down step by step and understand how generics work, why they’re important, and how you can use them effectively.

What Are Generics in Java?

Generics enable you to define a class, interface, or method with a placeholder for a data type. Instead of hardcoding a specific data type, you can define a generic type that gets replaced with an actual type when the code runs.

For example:

ArrayList<Integer> list = new ArrayList<>();

Here, ArrayList is a generic class, and <Integer> specifies the type of elements it will hold.

Without generics, you could accidentally insert the wrong type of object into a collection. With generics, the compiler ensures type safety, reducing runtime errors.

Why Use Generics?

Generics are useful for several reasons:

  1. Type Safety: You can ensure that only specific types of objects are used.

  2. Eliminate Type Casting: No need to manually cast objects when retrieving them from collections.

  3. Reusability: Write one version of code that works with multiple data types.

  4. Compile-Time Checking: Type-related errors are caught during compilation, not at runtime.

Example Without Generics

Before generics were introduced, Java collections used Object type. This meant you had to cast objects manually.

import java.util.ArrayList;

public class WithoutGenerics {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("Hello");
        list.add(100); // Allowed, but not type-safe

        String text = (String) list.get(0); // Casting required
        System.out.println(text);
    }
}

If you accidentally cast the wrong type, you’d get a runtime ClassCastException.

Example With Generics

Now let’s rewrite the same example using generics.

import java.util.ArrayList;

public class WithGenerics {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        // list.add(100); // Compile-time error

        String text = list.get(0); // No casting needed
        System.out.println(text);
    }
}

Here, ArrayList<String> ensures only strings are added to the list. If you try to add an integer, the compiler will show an error before running the program.

Generic Class Example

You can also create your own generic classes. Let’s say you want a class that can store and return any type of data.

public class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>();
        intBox.setValue(100);
        System.out.println("Integer value: " + intBox.getValue());

        Box<String> strBox = new Box<>();
        strBox.setValue("Java Generics");
        System.out.println("String value: " + strBox.getValue());
    }
}

Here, T is a type parameter, which can represent any data type. When you create an object, you specify what T should be.

Type Parameters in Generics

You can use single or multiple type parameters. Common naming conventions are:

  • T – Type

  • E – Element (used in collections)

  • K – Key

  • V – Value

Example with two type parameters:

public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }

    public static void main(String[] args) {
        Pair<Integer, String> pair = new Pair<>(1, "Java");
        System.out.println(pair.getKey() + " = " + pair.getValue());
    }
}

Generic Methods

You can also make methods generic. This is useful when only one method needs to be generic, not the entire class.

public class GenericMethodExample {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4};
        String[] strArray = {"A", "B", "C"};

        printArray(intArray);
        printArray(strArray);
    }
}

Here, <T> before the return type tells Java that this method is generic and can work with any type of array.

Bounded Type Parameters

Sometimes, you want to restrict what types can be used with generics. For example, a method should work only for numbers.

You can use bounded type parameters with the extends keyword.

public class BoundedType {
    public static <T extends Number> void showValue(T value) {
        System.out.println("Value: " + value);
    }

    public static void main(String[] args) {
        showValue(100);      // int
        showValue(45.67);    // double
        // showValue("Hello"); // Error
    }
}

Here, T extends Number means only classes that extend Number (like Integer, Double, Float) are allowed.

Wildcards in Generics

Wildcards (?) are used when you don’t know the exact type at compile time or when flexibility is needed.

Example:

import java.util.List;
import java.util.ArrayList;

public class WildcardExample {
    public static void displayList(List<?> list) {
        for (Object item : list) {
            System.out.println(item);
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(10);
        intList.add(20);

        List<String> strList = new ArrayList<>();
        strList.add("A");
        strList.add("B");

        displayList(intList);
        displayList(strList);
    }
}

The ? wildcard allows the method to accept any type of list.

Upper and Lower Bounded Wildcards

You can further restrict wildcards using extends and super.

  • Upper bounded wildcard (? extends Type) → accepts a type or its subclasses.

  • Lower bounded wildcard (? super Type) → accepts a type or its superclasses.

Example:

import java.util.*;

public class BoundedWildcard {
    public static void printNumbers(List<? extends Number> list) {
        for (Number n : list) {
            System.out.println(n);
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
        printNumbers(intList);
        printNumbers(doubleList);
    }
}

Advantages of Generics

  1. Compile-time type checking prevents runtime errors.

  2. Code reusability — write once, use for multiple types.

  3. No explicit casting needed.

  4. Improved readability and cleaner code.

Summary of the Tutorial

Generics make Java code safer, cleaner, and more reusable.
They help eliminate type casting and runtime errors while ensuring your collections and methods work smoothly across multiple data types.

In short:

  • Use <T> to define a generic type.

  • Apply generics to classes, methods, and interfaces.

  • Use bounded types to restrict allowed data types.

  • Use wildcards (?) for flexibility when working with collections.

Generics are an essential concept for writing modern Java code, especially when you start working with frameworks like Spring, Hibernate, and Java Collections Framework.


Practice Questions

  1. Write a generic Java class named Box<T> that can store and return any data type. Demonstrate it using both String and Integer values.

  2. Create a generic method named printArray() that prints all elements of an array, regardless of its type. Test it with arrays of integers, doubles, and strings.

  3. Write a Java program to create a generic class Pair<K, V> that stores a key-value pair and prints them.

  4. Create a generic method getLastElement() that returns the last element of any array passed to it.

  5. Write a generic program that accepts only subclasses of Number and prints the sum of all numbers in a list.

  6. Create a program that uses an unbounded wildcard (?) to print the contents of different types of lists.

  7. Write a program that demonstrates upper bounded wildcard (? extends Number) by printing numeric lists such as Integer, Double, and Float.

  8. Write a program that demonstrates lower bounded wildcard (? super Integer) by inserting integers into a list and printing them.

  9. Build a generic class DataHolder<T> with methods to set and get data. Create objects using String, Double, and Character types.

  10. Create a Java program that defines a generic method <T extends Comparable<T>> T findMax(T[] array) which returns the maximum element in any array of comparable objects.


Try a Short Quiz.

No quizzes available.


Go Back Top