Shiklo Programming
EN
  • English
  • Español
  • Português
  • Deutsch
  • Français
  • 日本語
  • हिन्दी

Java Generics

You have been using generics since ArrayList: the <String> in ArrayList<String> tells Java which type the list holds. The compiler then checks every add, and get gives back a String without any cast. Generics let one class or method work with many types while keeping that safety. In this topic you write your own.

Why generics

Without generics, a container would have to store Object, and every read would need a cast that may fail while the program runs. With generics, the mistake is caught by the compiler:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("Asha");
        names.add(42);
    }
}
The compiler says
Main.java:7: error: incompatible types: int cannot be converted to String

A generic class

Put a type parameter in angle brackets after the class name. By convention it is a single capital letter: T for type, E for element, K and V for key and value. Inside the class, T is used like any other type:

class Box<T> {
    private T value;

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

    T get() {
        return value;
    }

    boolean isEmpty() {
        return value == null;
    }
}

public class Main {
    public static void main(String[] args) {
        Box<String> word = new Box<>("Java");
        Box<Integer> number = new Box<>(23);
        System.out.println(word.get().length());
        System.out.println(number.get() + 1);
        System.out.println(new Box<String>(null).isEmpty());
    }
}
Output
4
24
true

word.get() is known to be a String and number.get() an Integer, so no casts are needed. Generic types work only with classes, which is why numbers use the wrapper classes.

A class can have several type parameters, like a pair of two values:

class Pair<A, B> {
    final A first;
    final B second;

    Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }

    @Override
    public String toString() {
        return "(" + first + ", " + second + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> p = new Pair<>("age", 31);
        System.out.println(p);
        System.out.println(p.first.toUpperCase() + " " + (p.second + 1));
    }
}
Output
(age, 31)
AGE 32

Generic methods

A single method can be generic too. The type parameter is written before the return type:

import java.util.List;

public class Main {
    static <T> T last(List<T> items) {
        return items.get(items.size() - 1);
    }

    static <T extends Comparable<T>> T largest(List<T> items) {
        T best = items.get(0);
        for (T item : items) {
            if (item.compareTo(best) > 0) {
                best = item;
            }
        }
        return best;
    }

    public static void main(String[] args) {
        System.out.println(last(List.of("a", "b", "c")));
        System.out.println(largest(List.of(4, 19, 7)));
        System.out.println(largest(List.of("pear", "apple", "plum")));
    }
}
Output
c
19
plum

<T extends Comparable<T>> is a bounded type: largest works only for types that can be compared, so compareTo from the Interfaces topic is allowed inside.

Wildcards

A List<Integer> is not a List<Number>, even though an Integer is a Number. To accept a list of any kind of number, use a wildcard: List<? extends Number> means "a list of some type that is a Number":

import java.util.List;

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

    public static void main(String[] args) {
        System.out.println(sum(List.of(1, 2, 3)));
        System.out.println(sum(List.of(1.5, 2.5)));
    }
}
Output
6.0
4.0

You will read wildcards much more often than you write them; Java's own library uses them a lot.

Practice exercises

  1. Write a generic class Stack<T> with push, pop and isEmpty, using an ArrayList<T> inside.
  2. Write a generic method swap(T[] array, int i, int j) and test it with strings and numbers.
  3. Write <T extends Comparable<T>> boolean isSorted(List<T> list).