Master Core Java Programming From Scratch

Clear, interactive, and structured coding lessons designed for absolute beginners.

Generics in Java

Learn how Java Generics provide type safety, reusable components, generic classes, methods, interfaces, wildcards, bounds, and type inference.

What are Generics?

Generics allow classes, interfaces, and methods to operate with parameterized types. They help catch type-related errors at compile time instead of relying on runtime casts.

Generics are widely used throughout the Java Collections Framework.

Java
List<String> names =
    new ArrayList<>();

names.add("Amit");
names.add("Neha");

Before Generics

Older Java code could store objects without specifying their intended type.

Java
List values = new ArrayList();

values.add("Java");
values.add(100);

String text = (String) values.get(0);

Raw collections lose useful compile-time type checking and often require explicit casts.

Using Generics

Java
List<String> values =
    new ArrayList<>();

values.add("Java");

String text = values.get(0);

The compiler knows that the list contains Strings, so an explicit cast is not required.

Type Safety

Java
List<String> names =
    new ArrayList<>();

names.add("Java");

// Compile-time error:
// names.add(100);

Generics prevent incompatible values from being added to a parameterized collection.

Generic Classes

A generic class declares one or more type parameters.

Java
class Box<T> {

    private T value;

    void set(T value) {

        this.value = value;

    }

    T get() {

        return value;

    }

}

Using a Generic Class

Java
Box<String> stringBox =
    new Box<>();

stringBox.set("Hello");

String text = stringBox.get();

System.out.println(text);
Output
Hello

Same Generic Class with Different Types

Java
Box<Integer> numberBox =
    new Box<>();

numberBox.set(100);

Box<Double> decimalBox =
    new Box<>();

decimalBox.set(10.5);

The same generic class can be reused with different reference types.

Multiple Type Parameters

A generic class can declare multiple type parameters.

Java
class Pair<K, V> {

    private K key;

    private V value;

    Pair(K key, V value) {

        this.key = key;
        this.value = value;

    }

    K getKey() {

        return key;

    }

    V getValue() {

        return value;

    }

}

Using Multiple Type Parameters

Java
Pair<Integer, String> student =
    new Pair<>(101, "Amit");

System.out.println(
    student.getKey()
);

System.out.println(
    student.getValue()
);

Generic Methods

A method can declare its own type parameter independently of the class.

Java
class Utility {

    static <T> void print(T value) {

        System.out.println(value);

    }

}

Calling a Generic Method

Java
Utility.print("Java");

Utility.print(100);

Utility.print(10.5);

Java can infer the appropriate type argument from the method invocation.

Generic Interfaces

Java
interface Repository<T> {

    void save(T value);

    T find();

}

class StringRepository
    implements Repository<String> {

    private String value;

    public void save(String value) {

        this.value = value;

    }

    public String find() {

        return value;

    }

}

Bounded Type Parameters

A type parameter can be restricted using an upper bound.

Java
class Calculator {

    static <T extends Number>
    double doubleValue(T value) {

        return value.doubleValue();

    }

}

Here, T must be a subtype of Number.

Multiple Bounds

A type parameter can have multiple bounds. A class, when present, must come first, followed by interfaces.

Java
<T extends Number & Comparable<T>>

Wildcards

The wildcard ? represents an unknown type.

Java
List<?> values;

A wildcard is useful when code needs to work with a collection whose exact element type is not known.

Unbounded Wildcard

Java
static void printList(
    List<?> values
) {

    for (Object value : values) {

        System.out.println(value);

    }

}

Upper-Bounded Wildcard

An upper-bounded wildcard uses ? extends Type.

Java
List<? extends Number> numbers;

Such a reference can point to a list of Number or any subtype of Number.

Lower-Bounded Wildcard

A lower-bounded wildcard uses ? super Type.

Java
List<? super Integer> values;

values.add(10);
values.add(20);

This is useful when a method needs to safely consume values of a particular type.

PECS Principle

PECS: Producer Extends, Consumer Super.
  • Use ? extends T when a generic structure produces values of type T.
  • Use ? super T when a generic structure consumes values of type T.

Diamond Operator

The diamond operator <> allows the compiler to infer generic type arguments in many object creation expressions.

Java
List<String> names =
    new ArrayList<>();

Raw Types

A raw type omits the generic type argument.

Java
List values = new ArrayList();
Raw types are mainly retained for compatibility with pre-generics Java code. New code should normally use parameterized types.

Type Erasure

Java implements generics primarily through type erasure. Generic type information used for compile-time checking is generally erased from ordinary class and method signatures at runtime.

This provides compatibility with older Java bytecode while still giving developers compile-time type safety.

Generics and Primitive Types

Java type arguments must be reference types, not primitive types.

Java
// Invalid:
// List<int> values;

// Correct:
List<Integer> values =
    new ArrayList<>();

Wrapper classes such as Integer, Double, and Boolean are used when primitive values need to participate in generic APIs.

Generics and Autoboxing

Java can automatically convert between primitive values and their corresponding wrapper types in many contexts.

Java
List<Integer> numbers =
    new ArrayList<>();

numbers.add(10);

int value = numbers.get(0);

Practical Example: Generic Stack

Java
class Stack<T> {

    private List<T> items =
        new ArrayList<>();

    void push(T item) {

        items.add(item);

    }

    T pop() {

        return items.remove(
            items.size() - 1
        );

    }

    boolean isEmpty() {

        return items.isEmpty();

    }

}

Using the Generic Stack

Java
Stack<String> stack =
    new Stack<>();

stack.push("Java");
stack.push("Spring");

System.out.println(stack.pop());
Output
Spring

Benefits of Generics

  • Provides compile-time type safety.
  • Reduces explicit casting.
  • Makes reusable classes and methods possible.
  • Improves readability of APIs.
  • Works naturally with Java collections.

Generics Best Practices

  • Prefer parameterized types over raw types.
  • Use meaningful type parameter names such as T, K, and V where appropriate.
  • Use bounded wildcards when they express the intended API relationship.
  • Follow the PECS principle for producer and consumer APIs.
  • Avoid unnecessary unchecked casts and raw-type usage.

Interview Questions

Generics allow types to be parameterized, enabling reusable and type-safe classes, interfaces, and methods.

Generics provide compile-time type checking, reduce casts, and allow reusable type-safe components.

The ? wildcard represents an unknown generic type. It can be unbounded or constrained using extends or super.

PECS stands for Producer Extends, Consumer Super. It is a common guideline for choosing between upper- and lower-bounded wildcards.

No. Generic type arguments must be reference types. Wrapper classes such as Integer are used instead of int.
Summary

Generics provide compile-time type safety and reusable parameterized components in Java. Important concepts include generic classes and methods, bounded type parameters, wildcards, PECS, type inference, raw types, and type erasure.