# Generics Fundamentals

Course: [Java](https://www.devtutorials.ai/learn/java) · Section: Generics
Source: https://www.devtutorials.ai/learn/java/generics/generics-fundamentals

> Write type-safe, reusable code with generic classes, methods and interfaces, bounded type parameters, wildcards, and generic collections.

Think about a lunch box with a label on the lid.

One box says "Sandwiches". Another says "Fruit". The box itself is the same design every time. Only the label changes. And because of the label, nobody puts soup in the sandwich box by mistake.

Generics work the same way. You write a class or method once, like the lunch box design. Then, each time you use it, you add a label that says what type it holds: `Box<String>`, `Box<Integer>`. Java checks the label and refuses the wrong type.

In simple words: generics let you write code once that works with many types, while Java still checks that each type is used correctly.

## What You Will Learn

In this lesson, you will learn:

1. What generics are, and the problems they solve.
2. How to write generic classes, methods and interfaces.
3. What type parameters like `T` and `E` mean.
4. How bounded type parameters limit which types are allowed.
5. How the wildcards `?`, `? extends` and `? super` work.
6. How generic collections like `List<String>` keep data safe.
7. How generic collections compare with the old, non-generic ones.

## Start With a Working Example

Press **Run**.

```java runnable title="One Box class, three different types"
public class Main {
    public static void main(String[] args) {
        Box<String> nameBox = new Box<>("Asha");
        Box<Integer> ageBox = new Box<>(21);
        Box<Double> priceBox = new Box<>(499.5);

        String name = nameBox.get();
        int age = ageBox.get();
        double price = priceBox.get();

        System.out.println(name + " is " + age);
        System.out.println("Price: " + price);
    }
}

class Box<T> {
    private T item;

    Box(T item) {
        this.item = item;
    }

    T get() {
        return item;
    }
}
```

Expected output:

```text
Asha is 21
Price: 499.5
```

`Box` is written once. The `<T>` after its name is a placeholder for a type. Each time you create a box, you fill in the real type: `String`, `Integer` or `Double`.

`nameBox.get()` returns a `String`, with no cast needed. Java already knows what is inside.

### Try This

Add the line `nameBox = new Box<>(42);`. The program no longer compiles, because `nameBox` is labelled `Box<String>` and `42` is not a `String`.

## Introduction to Generics

**Generics** let a class, interface or method work with a type that is chosen later, when the code is used.

You mark the "chosen later" type with a **type parameter** in angle brackets, such as `<T>`. When you use the code, you replace it with a real type, called a **type argument**.

```text
class Box<T> { ... }          // T is a type parameter

Box<String> b = ...;          // String is the type argument
```

You have already used generics without knowing:

- `ArrayList<String>` is a list that only holds strings.
- `HashMap<String, Integer>` maps strings to whole numbers.
- `Predicate<Integer>` tests whole numbers.

Generics were added in Java 5. Today, almost every collection and many other classes use them.

## Why Use Generics

Without generics, a general-purpose class has to use `Object`, the parent of every class. It can hold anything. That sounds handy, but it causes two problems.

### Problem 1: Casts Everywhere

When you take a value out of an `Object` container, Java only knows it is an `Object`. You must cast it back to the real type yourself.

### Problem 2: Mistakes Found Too Late

Nothing stops you putting the wrong type in. The mistake only shows up later, when the program runs and the cast fails with a `ClassCastException`.

```java runnable title="Without generics, every read needs a cast"
public class Main {
    public static void main(String[] args) {
        ObjectBox box = new ObjectBox("Ravi");

        String name = (String) box.get();
        System.out.println("Name: " + name);

        Object raw = box.get();
        System.out.println("Is it a String? " + (raw instanceof String));
    }
}

class ObjectBox {
    private Object item;

    ObjectBox(Object item) {
        this.item = item;
    }

    Object get() {
        return item;
    }
}
```

Expected output:

```text
Name: Ravi
Is it a String? true
```

`box.get()` returns an `Object`, so the code must cast it with `(String)`. If someone had stored an `Integer` instead, that cast would fail while the program runs.

With generics, the same mistake is caught by the compiler, before the program ever runs. Read the code, then look at the output below it.

```java readonly title="Generics catch the wrong type at compile time"
public class Main {
    public static void main(String[] args) {
        Box<String> box = new Box<>("Ravi");
        box.set(42);
    }
}

class Box<T> {
    private T item;
    Box(T item) { this.item = item; }
    void set(T item) { this.item = item; }
    T get() { return item; }
}
```

Output:

```text
Main.java:4: error: incompatible types: int cannot be converted to String
        box.set(42);
                ^
1 error
```

The compiler reports `incompatible types: int cannot be converted to String`.

### The Benefits

| Benefit | What it means |
|---|---|
| **Type safety** | The compiler rejects the wrong type, before the program runs |
| **No casts** | Values come out already as the right type |
| **Reuse** | One class or method works with many types |
| **Clear code** | `List<Student>` tells the reader exactly what the list holds |

## Generic Classes

A **generic class** has one or more type parameters after its name.

```text
class ClassName<T> {
    // T can be used as a type inside the class
}
```

Inside the class, `T` works like a real type. You can use it for fields, parameters, return types and local variables.

### The Diamond Operator

When you create an object, you can leave the angle brackets on the right side empty: `new Box<>("Asha")`. Java works out the type from the left side. The empty `<>` is called the **diamond operator**.

### Several Type Parameters

A class can have more than one type parameter, separated by commas.

```java runnable title="A Pair holds two values of different types"
public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> score = new Pair<>("Asha", 92);
        Pair<String, String> capital = new Pair<>("India", "New Delhi");
        Pair<Integer, Boolean> check = new Pair<>(17, false);

        System.out.println(score.getFirst() + " scored " + score.getSecond());
        System.out.println("Capital of " + capital.getFirst() + ": " + capital.getSecond());
        System.out.println(check.getFirst() + " is even? " + check.getSecond());
    }
}

class Pair<K, V> {
    private final K first;
    private final V second;

    Pair(K first, V second) {
        this.first = first;
        this.second = second;
    }

    K getFirst() { return first; }
    V getSecond() { return second; }
}
```

Expected output:

```text
Asha scored 92
Capital of India: New Delhi
17 is even? false
```

`Pair<K, V>` has two type parameters. Each pair chooses its own two types.

Type arguments must be **classes**, not primitives. Write `Box<Integer>`, not `Box<int>`. Java turns `int` values into `Integer` objects and back automatically, which is called **autoboxing**, so `new Box<>(21)` still works.

| Primitive | Wrapper class to use in generics |
|---|---|
| `int` | `Integer` |
| `double` | `Double` |
| `char` | `Character` |
| `boolean` | `Boolean` |
| `long` | `Long` |

## Generic Methods

A **generic method** has its own type parameter, declared **before the return type**.

```text
static <T> void printTwice(T value) { ... }
```

The method can be inside an ordinary, non-generic class. Its type parameter only exists inside that one method.

When you call it, you usually do not write the type. Java works it out from the arguments. This is called **type inference**.

```java runnable title="One method works with any type of array"
public class Main {
    public static void main(String[] args) {
        String[] cities = {"Chennai", "Pune", "Kochi"};
        Integer[] marks = {72, 88, 95};
        Character[] grades = {'A', 'B', 'A'};

        printAll(cities);
        printAll(marks);
        printAll(grades);

        System.out.println("Last city: " + lastItem(cities));
        System.out.println("Last mark: " + lastItem(marks));
    }

    static <T> void printAll(T[] items) {
        for (T item : items) {
            System.out.print(item + " ");
        }
        System.out.println();
    }

    static <T> T lastItem(T[] items) {
        return items[items.length - 1];
    }
}
```

Expected output:

```text
Chennai Pune Kochi
72 88 95
A B A
Last city: Kochi
Last mark: 95
```

`printAll` is written once and works on three different array types.

`lastItem` returns `T`. When it gets a `String[]`, it returns a `String`. When it gets an `Integer[]`, it returns an `Integer`. No cast is needed either way.

## Generic Interfaces

An interface can have type parameters too. Each class that implements it chooses the real type.

Java's own `Comparable<T>` is a generic interface. A class implements `Comparable<Student>` to say "students can be compared with other students".

```java runnable title="Each class picks the type for a generic interface"
public class Main {
    public static void main(String[] args) {
        Repository<String> names = new NameStore();
        names.save("Asha");
        names.save("Ravi");
        System.out.println("Names: " + names.count());

        Repository<Integer> scores = new ScoreStore();
        scores.save(85);
        System.out.println("Scores: " + scores.count());
    }
}

interface Repository<T> {
    void save(T item);
    int count();
}

class NameStore implements Repository<String> {
    private int total = 0;

    public void save(String item) {
        System.out.println("Saving name " + item);
        total++;
    }

    public int count() {
        return total;
    }
}

class ScoreStore implements Repository<Integer> {
    private int total = 0;

    public void save(Integer item) {
        System.out.println("Saving score " + item);
        total++;
    }

    public int count() {
        return total;
    }
}
```

Expected output:

```text
Saving name Asha
Saving name Ravi
Names: 2
Saving score 85
Scores: 1
```

`NameStore` fills in `T` with `String`, so its `save` method takes a `String`. `ScoreStore` fills in `T` with `Integer`.

A class can also stay generic itself: `class MemoryStore<T> implements Repository<T>` works for any type.

## Type Parameters

A **type parameter** is the placeholder name inside the angle brackets.

By convention, type parameters are **single capital letters**. This makes them easy to tell apart from real class names.

| Letter | Usually means | Example |
|---|---|---|
| `T` | Type | `Box<T>` |
| `E` | Element (of a collection) | `List<E>` |
| `K` | Key | `Map<K, V>` |
| `V` | Value | `Map<K, V>` |
| `N` | Number | `Calculator<N>` |
| `R` | Return type | `Function<T, R>` |
| `S`, `U` | Second and third types | `Pair<T, U>` |

These are only conventions. `Box<Thing>` would compile, but it looks like a real class called `Thing`, which is confusing.

### Where a Type Parameter Can Be Used

Inside a generic class, `T` can be the type of:

- a field: `private T item;`
- a parameter: `void set(T item)`
- a return value: `T get()`
- a local variable: `T copy = item;`

You **cannot**:

- create an object of it: `new T()` does not compile;
- use it in a static field: `static T shared;` does not compile, because static members belong to the class, not to one `Box<String>` or `Box<Integer>`.

## Bounded Type Parameters

Sometimes a method only makes sense for certain types. Adding up values only works for numbers. Comparing values only works for things that can be compared.

A **bounded type parameter** limits the allowed types with `extends`:

```text
<T extends Number>
```

This means "`T` can be `Number` or any child of `Number`", such as `Integer`, `Double` or `Long`.

Because Java now knows `T` is some kind of `Number`, you can call `Number` methods on it, such as `doubleValue()`.

```java runnable title="Only numbers are allowed, so number methods can be used"
public class Main {
    public static void main(String[] args) {
        Integer[] marks = {70, 85, 90};
        Double[] prices = {19.5, 30.0, 50.5};

        System.out.println("Average mark: " + average(marks));
        System.out.println("Average price: " + average(prices));
    }

    static <T extends Number> double average(T[] values) {
        double total = 0;
        for (T v : values) {
            total += v.doubleValue();
        }
        return total / values.length;
    }
}
```

Expected output:

```text
Average mark: 81.66666666666667
Average price: 33.333333333333336
```

`average` accepts an `Integer[]` and a `Double[]`. Calling it with a `String[]` does not compile, because `String` is not a `Number`.

Without the bound, `v.doubleValue()` would not compile. Java would only know `v` is some unknown type.

### Bounding by an Interface

The bound can be an interface too. For interfaces, you still write `extends`, not `implements`.

```java runnable title="T must be Comparable, so compareTo can be used"
public class Main {
    public static void main(String[] args) {
        System.out.println(largest(42, 17, 99, 63));
        System.out.println(largest("mango", "apple", "kiwi"));
        System.out.println(largest(3.5, 2.25));
    }

    @SafeVarargs
    static <T extends Comparable<T>> T largest(T... items) {
        T best = items[0];
        for (T item : items) {
            if (item.compareTo(best) > 0) {
                best = item;
            }
        }
        return best;
    }
}
```

Expected output:

```text
99
mango
3.5
```

`T extends Comparable<T>` means "any type that can be compared with itself". `Integer`, `String` and `Double` all qualify.

`"mango"` is the largest string, because it comes last in dictionary order.

`@SafeVarargs` tells the compiler this varargs method is safe with generics, which hides a warning.

A type can have **several bounds**, joined with `&`: `<T extends Number & Comparable<T>>`. A class bound must come first.

## Wildcards

A **wildcard**, written `?`, stands for "some type I do not need to name". It is used in variable and parameter types, most often for method parameters.

Why are wildcards needed? Because of a surprising rule:

```text
List<Integer> is NOT a List<Number>
```

Even though `Integer` is a child of `Number`, a list of integers is not a child of a list of numbers. If it were, you could add a `Double` to a list that must only hold integers.

So a method that takes `List<Number>` will not accept a `List<Integer>`. Wildcards solve this.

### Unbounded Wildcard (?)

`List<?>` means "a list of some type, I do not care which".

Use it when the method only needs things every object has, such as `size()` or `toString()`.

You can **read** items from a `List<?>`, but only as `Object`. You cannot **add** anything except `null`, because Java does not know which type is allowed.

### Upper-Bounded Wildcard (? extends)

`List<? extends Number>` means "a list of `Number`, or of any child of `Number`".

It accepts `List<Integer>`, `List<Double>` and `List<Number>`.

You can **read** items as `Number`. You cannot **add** items, because the list might really be a `List<Integer>`, and a `Double` would not belong.

### Lower-Bounded Wildcard (? super)

`List<? super Integer>` means "a list of `Integer`, or of any **parent** of `Integer`".

It accepts `List<Integer>`, `List<Number>` and `List<Object>`.

You can **add** `Integer` values, because every one of those lists can hold an `Integer`. When you read, you only get `Object`.

```java runnable title="Three wildcards, three different jobs"
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> marks = List.of(70, 85, 90);
        List<Double> prices = List.of(19.5, 30.5);
        List<String> names = List.of("Asha", "Ravi");

        printSize(marks);
        printSize(names);

        System.out.println("Sum of marks: " + sum(marks));
        System.out.println("Sum of prices: " + sum(prices));

        List<Number> numbers = new ArrayList<>();
        addTens(numbers);
        List<Object> things = new ArrayList<>();
        addTens(things);
        System.out.println("Numbers: " + numbers);
        System.out.println("Things: " + things);
    }

    static void printSize(List<?> list) {
        System.out.println("List has " + list.size() + " items");
    }

    static double sum(List<? extends Number> list) {
        double total = 0;
        for (Number n : list) {
            total += n.doubleValue();
        }
        return total;
    }

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

Expected output:

```text
List has 3 items
List has 2 items
Sum of marks: 245.0
Sum of prices: 50.0
Numbers: [10, 20]
Things: [10, 20]
```

- `printSize` works on any list, because it only asks for the size.
- `sum` reads numbers from a list of integers and from a list of doubles.
- `addTens` writes integers into a list of numbers and into a list of objects.

### PECS: Producer Extends, Consumer Super

A simple rule tells you which wildcard to choose:

- If the list **produces** values for you to read, use **`extends`**.
- If the list **consumes** values you write into it, use **`super`**.
- If you do neither, use **`?`** on its own.

| Wildcard | Accepts | Can read as | Can add |
|---|---|---|---|
| `List<?>` | A list of any type | `Object` | Only `null` |
| `List<? extends Number>` | `Number` or its children | `Number` | Nothing (except `null`) |
| `List<? super Integer>` | `Integer` or its parents | `Object` | `Integer` values |

**Easy Way to Remember:** **PECS**, **P**roducer **E**xtends, **C**onsumer **S**uper. Reading out? `extends`. Putting in? `super`.

## Generic Collections

The **Collections Framework** in `java.util` is Java's set of ready-made data structures. Every one of them is generic.

| Collection | What it holds | Example |
|---|---|---|
| `List<E>` / `ArrayList<E>` | An ordered list, duplicates allowed | `List<String> names` |
| `Set<E>` / `HashSet<E>` | Unique items, no duplicates | `Set<String> tags` |
| `Map<K, V>` / `HashMap<K, V>` | Key-value pairs | `Map<String, Integer> stock` |
| `Queue<E>` / `ArrayDeque<E>` | Items in arrival order | `Queue<String> tickets` |

You choose the element type in angle brackets. The collection then accepts and returns only that type.

```java runnable title="A list, a set and a map, each with its own types"
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        List<String> orders = new ArrayList<>();
        orders.add("Pen");
        orders.add("Bag");
        orders.add("Pen");
        System.out.println("Orders: " + orders);

        Set<String> uniqueItems = new HashSet<>(orders);
        System.out.println("Different items: " + uniqueItems.size());

        Map<String, Integer> prices = new HashMap<>();
        prices.put("Pen", 20);
        prices.put("Bag", 499);

        int total = 0;
        for (String item : orders) {
            total += prices.get(item);
        }
        System.out.println("Total: " + total);
    }
}
```

Expected output:

```text
Orders: [Pen, Bag, Pen]
Different items: 2
Total: 539
```

`prices.get(item)` returns an `Integer`, not an `Object`. So it can be added to `total` straight away, with no cast.

You will study each collection in depth in the next chapter.

It is good practice to use the **interface** type on the left, like `List<String> names = new ArrayList<>();`. Then you can switch to another kind of list later by changing only the right side.

## Generics vs Non-Generic Collections

Before Java 5, collections had no type parameter. They held `Object`. Using a collection without its type argument is called a **raw type**:

```text
ArrayList list = new ArrayList();   // raw type: holds anything
```

Raw types still compile, so old code keeps working. But you lose every benefit of generics, and the compiler warns you about **unchecked** operations. Read the code, then look at the output below it.

```java readonly title="A raw list accepts anything, a generic list does not"
import java.util.ArrayList;
import java.util.List;

public class Main {
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void main(String[] args) {
        List rawList = new ArrayList();
        rawList.add("Asha");
        rawList.add(42);
        rawList.add(true);
        System.out.println("Raw list: " + rawList);

        String first = (String) rawList.get(0);
        System.out.println("Needed a cast to read: " + first);

        List<String> safeList = new ArrayList<>();
        safeList.add("Asha");
        safeList.add("Ravi");
        String name = safeList.get(0);
        System.out.println("No cast needed: " + name);
    }
}
```

Output:

```text
Raw list: [Asha, 42, true]
Needed a cast to read: Asha
No cast needed: Asha
```

The raw list happily held a `String`, an `int` and a `boolean`. Reading from it needed a cast. If the code had cast `rawList.get(1)` to `String`, the program would have stopped with a `ClassCastException`.

`safeList` only accepts strings, and reading from it needs no cast. `safeList.add(42)` would not compile.

`@SuppressWarnings` hides the compiler's warnings for this demonstration. In your own code, do not hide them. Fix the raw type instead.

| | Non-generic (raw) collection | Generic collection |
|---|---|---|
| Written as | `ArrayList list` | `ArrayList<String> list` |
| Holds | Any object | Only the chosen type |
| Wrong type added | Allowed | Compile error |
| Reading a value | Needs a cast | No cast |
| Errors found | While the program runs (`ClassCastException`) | When the code is compiled |
| Compiler warnings | Yes, "unchecked" | No |
| Use today? | Only in very old code | Always |

## Common mistakes

The following mistakes are common when writing generic classes and methods. Each row shows what you see, why it happens and how to fix it.

| Symptom | Cause | Fix |
|---|---|---|
| `unexpected type; required: reference, found: int` | A primitive was used as a type argument | Use the wrapper class, like `Integer` |
| `List<Integer> cannot be converted to List<Number>` | Generic types do not follow inheritance | Use `List<? extends Number>` |
| Cannot add to a list parameter | The parameter uses `? extends` | Use `? super` for lists you write into |
| `unexpected type` on `new T()` | Type parameters cannot be instantiated | Pass in a `Supplier<T>` or a `Class<T>` |
| `cannot find symbol` on a method of `T` | `T` has no bound | Add a bound: `<T extends Number>` |
| "unchecked" warnings | A raw type was used | Add the type argument |

## What to Remember

- Generics let you write a class, interface or method once and use it with many types, safely.
- The compiler checks the type, so wrong types are caught early and no casts are needed.
- Type parameters, like `T`, `E`, `K` and `V`, are placeholders. Type arguments must be classes, like `Integer`.
- Generic methods declare `<T>` before the return type. Java infers `T` from the arguments.
- Bounds like `<T extends Number>` limit the allowed types and unlock their methods.
- Wildcards: `?` for any type, `? extends` to read, `? super` to write (PECS).
- Always use generic collections like `List<String>`. Raw types are only for very old code.

Next: Collections Fundamentals
