Generics in Java: Type Safety and Flexibility

Generics in Java allow you to define classes, methods, and interfaces with type parameters. They provide stronger type checks at compile time, reducing errors related to type casting and enhancing code reusability.

1. What are Generics?

Generics enable classes, interfaces, and methods to operate on objects of various types while providing compile-time type safety. Instead of working with raw types, you specify type parameters to make your code more flexible and reusable.

2. Need for Generics

3. Generic Collections

Using generics with collections ensures type safety and avoids the need for type casting. For example, instead of using raw collections, you can specify the type of elements the collection will hold.


import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        // list.add(123); // Compile-time error

        for (String item : list) {
            System.out.println(item);
        }
    }
}
      

4. Create a Generic Class

A generic class can hold objects of any type. Here's an example of a simple generic class:


public class GenericBox<T> {
    private T value;

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

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        GenericBox<String> box = new GenericBox<>();
        box.setValue("Generics in Java");
        System.out.println(box.getValue());
    }
}
      

5. Create Generic Methods

Generic methods allow you to specify type parameters in methods to handle different types of data.


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

    public static void main(String[] args) {
        String[] names = {"Alice", "Bob", "Charlie"};
        Integer[] numbers = {1, 2, 3};

        printArray(names);
        printArray(numbers);
    }
}
      

6. Bounded Types

In generics, you can restrict the types allowed for a parameter using bounded types. For example, you can restrict the type to subclasses of a specific class.


public class Main {
    public static <T extends Number> void printDouble(T number) {
        System.out.println(number.doubleValue());
    }

    public static void main(String[] args) {
        printDouble(10);       // Integer
        printDouble(10.5);     // Double
        // printDouble("text"); // Compile-time error
    }
}
      

7. Wildcards

Wildcards in generics provide flexibility when you want to allow multiple types. There are three types of wildcards in Java:

Unbounded Wildcard


public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}
      

Upper-Bounded Wildcard

Use `? extends Type` to specify that the type is a subclass of the given class.


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

Lower-Bounded Wildcard

Use `? super Type` to specify that the type is a superclass of the given class.


public static void addNumbers(List<? super Integer> list) {
    list.add(10);
    list.add(20);
}
      

8. Conclusion

Generics in Java provide compile-time type safety, allowing you to write more flexible and reusable code. They are widely used in the Java Collections Framework, making it easier to work with different types of data in a type-safe manner.