☕ Java Q50 / 162

What are generics in Java?

AI-Powered Answer ✓ Answered

Generics in Java are a powerful feature introduced in J2SE 5.0 that allows you to write classes, interfaces, and methods that operate on types as parameters. This promotes type safety and code reusability by catching type-related errors at compile time rather than runtime.

What are Generics?

Generics enable the definition of classes, interfaces, and methods where the type of data they operate on is specified as a parameter. Instead of writing code that works only with a specific type (e.g., String or Integer), you can write code that works with 'type parameters' like T (for Type) or E (for Element). This makes your code more flexible and prevents ClassCastException at runtime by enforcing type checks during compilation.

Key Benefits of Generics

  • Type Safety: Generics ensure that you only store objects of the specified type in a collection or use them with a generic class/method. The compiler checks for type compatibility at compile time, eliminating the need for explicit type casting and significantly reducing ClassCastException at runtime.
  • Elimination of Explicit Casts: Without generics, retrieving an object from a collection (like ArrayList) would often require casting it to its original type, which is cumbersome and error-prone. Generics remove this necessity, making code cleaner and easier to read.
  • Code Reusability: A single generic class or method can operate on objects of various types, reducing redundant code and making the codebase cleaner and more maintainable.

How Generics Work: Type Erasure

Java generics are implemented using a technique called type erasure. This means that type parameters are only present at compile time and are removed (erased) during the compilation process, converting generic types to their raw types (e.g., List<String> becomes List, and T becomes Object). This ensures backward compatibility with older Java versions that didn't support generics, but it also introduces some limitations, such as not being able to determine the actual type argument at runtime.

Common Uses of Generics

Generic Classes

You can create generic classes that can work with different types. The type parameter is specified when an object of the class is instantiated.

java
class Box<T> {
    private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}

// Usage
Box<Integer> integerBox = new Box<>();
integerBox.setContent(123);
int value = integerBox.getContent(); // No cast needed

Box<String> stringBox = new Box<>();
stringBox.setContent("Hello Generics");
String message = stringBox.getContent();

Generic Methods

Methods can also be made generic, allowing them to operate on various types. The type parameter is declared before the return type of the method.

java
public class Util {
    public static <T> T getFirstElement(T[] array) {
        if (array != null && array.length > 0) {
            return array[0];
        }
        return null;
    }

    public static <K, V> void printPair(K key, V value) {
        System.out.println("Key: " + key + ", Value: " + value);
    }
}

// Usage
Integer[] numbers = {1, 2, 3};
Integer firstNum = Util.getFirstElement(numbers);

Util.printPair("Name", "Alice");

Bounded Type Parameters and Wildcards

Sometimes you need to restrict the types that can be used as type arguments. Bounded type parameters (e.g., <T extends Number>) allow specifying an upper bound. Wildcards (?) provide more flexibility when dealing with generic types, especially in method signatures.

  • ? (Unbounded Wildcard): Represents an unknown type. E.g., List<?> can hold a list of any type.
  • ? extends T (Upper Bounded Wildcard): Specifies that the type argument must be T or a subtype of T. E.g., List<? extends Number> can hold List<Integer>, List<Double>, etc. (Use for 'reading' values).
  • ? super T (Lower Bounded Wildcard): Specifies that the type argument must be T or a supertype of T. E.g., List<? super Integer> can hold List<Integer>, List<Number>, List<Object>, etc. (Use for 'writing' values).
java
import java.util.ArrayList;
import java.util.List;

public class WildcardExample {
    public static void printList(List<?> list) {
        for (Object elem : list) {
            System.out.print(elem + " ");
        }
        System.out.println();
    }

    public static double sumOfNumbers(List<? extends Number> list) {
        double sum = 0.0;
        for (Number num : list) {
            sum += num.doubleValue();
        }
        return sum;
    }

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

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        printList(integers); // Output: 1 2 

        List<Double> doubles = new ArrayList<>();
        doubles.add(3.14);
        doubles.add(2.71);
        printList(doubles); // Output: 3.14 2.71 

        System.out.println("Sum of integers: " + sumOfNumbers(integers)); // Output: 3.0
        System.out.println("Sum of doubles: " + sumOfNumbers(doubles));   // Output: 5.85

        List<Object> objectList = new ArrayList<>();
        addIntegers(objectList);
        System.out.println("Object List after adding integers: " + objectList); // Output: [10, 20]
    }
}

Limitations and Considerations

  • Cannot Instantiate Generic Types with Primitive Types: You cannot use primitive types (like int, char, boolean) as type arguments; you must use their wrapper classes (e.g., Integer, Character, Boolean). List<int> is invalid, but List<Integer> is valid.
  • Cannot Create Instances of Type Parameters: You cannot create an object of a type parameter directly, e.g., new T() is not allowed due to type erasure.
  • Cannot Create Arrays of Generic Types: You cannot create arrays of generic types directly, e.g., new T[10] or new List<String>[10] are not allowed.
  • Cannot Use instanceof with Generic Types: Due to type erasure, the runtime type information is lost, so obj instanceof List<String> is not possible. You can only use obj instanceof List<?>.
  • Static Fields Cannot Be Generic: A static field cannot use a generic type parameter from the enclosing class, as static fields are shared across all instances, regardless of their type arguments.