Understanding Java Generics

Afzal ShaikAfzal Shaik
4 min read

Introduction to Java Generics

Generics were introduced in J2SE 5.0 to provide compile-time type safety and reduce programming errors. Before generics, there was no way to restrict the types of objects that could be stored in collections. This allowed for errors and runtime exceptions.

Generics allow you to declare type parameters that define and constrain the types of objects stored in a collection. This results in a type-safe code that is validated at compile time.

Advantages of Generics

• Type-safety: Generics ensure that only a single type of object is stored in a collection. This prevents incompatible types from being stored in the same collection.

• Eliminates casting: There is no need to cast when retrieving objects from a generic collection. The correct type is inferred at compile time.

• Compile-time checking: Potential issues are detected at compile time rather than runtime. This results in fewer runtime exceptions and more stable code.

• Reusability: Generic code can be reused with different types. A single generic method or class can operate on a variety of types.

Generic Classes and Methods

In addition to generic types (like List), you can also have generic classes and methods.

A generic class specifies its type parameters within angle brackets after the name of the class. For example:

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

The Box class can then be instantiated with any type, like:

Box<Integer> intBox = new Box<>();
intBox.setItem(5);
int intVal = intBox.getItem();

A generic method specifies its type parameters within angle brackets before the method name. For example:

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

This method can print any type of array. You call it by passing the actual type in angle brackets, like:

String[] strings = {"Hello", "World"};
Integer[] ints = {1, 2, 3};

printArray<String>(strings);
printArray<Integer>(ints);

Wildcards in Generics

Wildcards allow for flexibility and reusability of generic code. They are represented by '?' and can be used as type arguments for:

  • Local variables

  • Return types

  • Fields

  • Parameters

However, they cannot be used to invoke generic methods or create generic instances.

There are two types of wildcards:

  • ? extends T: Represents any subtype of T.

  • ? super T: Represents any supertype of T.

For example:

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

The printList method can print a List of any subclass of Number, like Integer, Double, etc.

Type Erasure in Java Generics

Java implements generics using type erasure. This means that generics are only available at compile time, and after compilation, the generic type information is lost. i.e. Java generic collections are not stored with a type to ensure backward compatibility with pre-J2SE 5.0. Type information is removed when added to a generic collection. This is called Type Erasure.

This means that a generic collection can be assigned to a non-generic reference and objects in a generic typed collection can be placed in a non-generic collection.

This means that at runtime:

  • The generic type, like String, is changed to its upper bound, Object.

  • All type checks and casts are performed at compile-time.

  • The JVM only sees the raw type, List. It has no knowledge of the generic type String.

For example:

List<String> stringList = new ArrayList<String>();
stringList.add("Hello");

String s = stringList.get(0); // Compile-time cast to String

Object o = stringList.get(0); // Works fine at runtime

Here, the compiler inserts a cast to String when getting the element at index 0.

Conclusion

Generics add a lot of power and flexibility to Java. They allow you to create reusable components that can work with various types of objects. Generics also add type safety and reduce programming errors by enforcing type constraints at compile time. Mastering generics is key to writing robust and reusable libraries and applications in Java.
TLDR: All Java generics really do is make sure you can't add the wrong type to a generic list and saves you from doing an explicit cast on retrieval, even though it is still done implicitly.

1
Subscribe to my newsletter

Read articles from Afzal Shaik directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Afzal Shaik
Afzal Shaik