Java + Spring Boot track

Lesson 13 · Part 3: Collections & Generics

Generics

Generics are a mechanism that defers "which type to deal with" to the time of defining class/method. The brackets in List are the placeholders. It is safe because the…

Part 3: Collections & Generics4 min readFree lesson

Generics are a mechanism that defers "which type to deal with" to the time of defining class/method. The brackets in List<String> are the placeholders. It is safe because the type is enforced at compile time, and can handle a variety of types, so it has high reusability.

Learning Objectives

  • Define the generic class Box<T>
  • Write a generic method <T> T choose(T a, T b)
  • Distinguish between wildcards ?, ? extends T, and ? super T.
  • Know the effect of bounded type (<T extends Number>)
  • A glimpse at the concept of type erasure

Key concepts

1) Generic class

public class Box<T> {
    private T value;
    public void set(T v) { this.value = v; }
    public T get() { return value; }
}

Box<String> b = new Box<>();
b.set("hi");
String s = b.get();      //No casting required

2) Generic method

static <T> T choose(boolean first, T a, T b) {
    return first ? a : b;
}
String r = choose(true, "yes", "no");

3) Wildcard

List<? extends Number> nums = ...;   //Number or its children (close to read-only)
List<? super Integer> ints = ...;     //Integer or its parent (writable)

There is a memorized rule called PECS: Producer-Extends, Consumer-Super. When the value is taken out, it is extends, and when it is put in, it is super.

4) bounded type

static <T extends Number> double sum(List<T> xs) {
    double s = 0;
    for (T x : xs) s += x.doubleValue();
    return s;
}

You can only receive Number or its children, so you can safely call parent methods like doubleValue().

5) Type erasure

Generic information is lost at runtime (List<String> ↔ List<Integer> appears to be the same class). So a code like new T() is not possible.

See as example

Example 1 — BoxGeneric.java: Generic class

public class BoxGeneric {
    static class Box<T> {
        private T value;
        public void set(T v) { this.value = v; }
        public T get() { return value; }
    }

    public static void main(String[] args) {
        Box<String> s = new Box<>();
        s.set("Hello");
        System.out.println(s.get());

        Box<Integer> n = new Box<>();
        n.set(42);
        System.out.println(n.get() + 1);
    }
}

Execution result

Hello
43

Note: The diamond operator <> infers the right-hand type.

Example 2 — GenericMethod.java: Generic method

import java.util.List;

public class GenericMethod {
    static <T> T first(List<T> list) {
        return list.isEmpty() ? null : list.get(0);
    }

    public static void main(String[] args) {
        System.out.println(first(List.of(1, 2, 3)));
        System.out.println(first(List.of("a", "b")));
        Object none = first(List.of());
        System.out.println(none);
    }
}

Execution result

1
a
null

Note: <T> in the method signature is inferred when calling the method.

Example 3 — Wildcards.java : ? extends and ? super

import java.util.ArrayList;
import java.util.List;

public class Wildcards {
    /** Sum any list of numbers.*/
    static double sum(List<? extends Number> xs) {
        double s = 0;
        for (Number n : xs) s += n.doubleValue();
        return s;
    }

    /** Add 1 to 3 to Integer or its parent.*/
    static void addNums(List<? super Integer> xs) {
        xs.add(1); xs.add(2); xs.add(3);
    }

    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)));

        List<Number> ns = new ArrayList<>();
        addNums(ns);
        System.out.println(ns);
    }
}

Execution result

6.0
4.0
[1, 2, 3]

Memo: extends when taken out and used, super when inserted.

Example 4 — BoundedType.java : T extends Number

import java.util.List;

public class BoundedType {
    static <T extends Number> double average(List<T> xs) {
        if (xs.isEmpty()) return 0;
        double s = 0;
        for (T x : xs) s += x.doubleValue();
        return s / xs.size();
    }

    public static void main(String[] args) {
        System.out.println(average(List.of(10, 20, 30)));
        System.out.println(average(List.of(1.5, 2.5, 3.5)));
    }
}

Execution result

20.0
2.5

Note: Thanks to T extends Number, you can handle Integer/Double/Long etc. in one method.

Common Mistakes

  1. Misunderstanding that Box<Object> and Box<String> are compatible (unrelated)
  2. Try new T() / new T[10] → Not possible due to type erasure
  3. Use of raw type (List list = ...) → Compilation warning + runtime risk
  4. Call add to collection ? extends T (most likely blocked)
  5. Overuse of wildcards in method return types → inconvenience to the caller

Cleanup

  • Generics are a tool for compile-time type safety
  • <T> is available at class/method level
  • Wildcards are easy to memorize using PECS rules.
  • Types are erased at runtime, so some restrictions exist.

Try it yourself

cd 03_Collection_Generic/13_Generic/src
javac BoxGeneric.java
java BoxGeneric

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/BoundedType.javaJava
import java.util.List;

public class BoundedType {
    static <T extends Number> double average(List<T> xs) {
        if (xs.isEmpty()) return 0;
        double s = 0;
        for (T x : xs) s += x.doubleValue();
        return s / xs.size();
    }

    public static void main(String[] args) {
        System.out.println(average(List.of(10, 20, 30)));
        System.out.println(average(List.of(1.5, 2.5, 3.5)));
    }
}
src/BoxGeneric.javaJava
public class BoxGeneric {
    static class Box<T> {
        private T value;
        public void set(T v) { this.value = v; }
        public T get() { return value; }
    }

    public static void main(String[] args) {
        Box<String> s = new Box<>();
        s.set("Hello");
        System.out.println(s.get());

        Box<Integer> n = new Box<>();
        n.set(42);
        System.out.println(n.get() + 1);
    }
}
src/GenericMethod.javaJava
import java.util.List;

public class GenericMethod {
    static <T> T first(List<T> list) {
        return list.isEmpty() ? null : list.get(0);
    }

    public static void main(String[] args) {
        System.out.println(first(List.of(1, 2, 3)));
        System.out.println(first(List.of("a", "b")));
        Object none = first(List.of());
        System.out.println(none);
    }
}
src/Wildcards.javaJava
import java.util.ArrayList;
import java.util.List;

public class Wildcards {
    /** Sum any list of numbers.*/
    static double sum(List<? extends Number> xs) {
        double s = 0;
        for (Number n : xs) s += n.doubleValue();
        return s;
    }

    /** Add 1 to 3 to Integer or its parent.*/
    static void addNums(List<? super Integer> xs) {
        xs.add(1); xs.add(2); xs.add(3);
    }

    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)));

        List<Number> ns = new ArrayList<>();
        addNums(ns);
        System.out.println(ns);
    }
}

Assignment

Problem 1 — Pair<A, B>

  • File name: Homework01.java
  • Key concepts: generic class, two type parameters

Requirements

  • Generic class Pair<A, B> definition (constructor + 2 getters + toString).
  • Create and print two (1, "one") and ("k", 3.14).

Expected output

(1, one)
(k, 3.14)

Problem 2 — Generic method max

  • File name: Homework02.java
  • Key concepts: generic methods, Comparable

Requirements

  • Define <T extends Comparable<T>> T max(List<T> xs) (null if empty list).
  • Call and output both Integer and String lists.

Expected output

9
ZZZ
null

Check the correct answer

After solving it yourself, compare it with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

Homework01.javaJava
/** Pair<A,B> generic class.*/
public class Homework01 {
    static class Pair<A, B> {
        private final A first;
        private final B second;

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

        A first() { return first; }
        B second() { return second; }

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

    public static void main(String[] args) {
        Pair<Integer, String> p1 = new Pair<>(1, "one");
        Pair<String, Double> p2 = new Pair<>("k", 3.14);
        System.out.println(p1);
        System.out.println(p2);
    }
}
Homework02.javaJava
import java.util.List;

/** Generic method max.*/
public class Homework02 {
    static <T extends Comparable<T>> T max(List<T> xs) {
        if (xs.isEmpty()) return null;
        T best = xs.get(0);
        for (T x : xs) if (x.compareTo(best) > 0) best = x;
        return best;
    }

    public static void main(String[] args) {
        System.out.println(max(List.of(3, 9, 1, 7)));
        System.out.println(max(List.of("ABC", "ZZZ", "Yellow")));
        System.out.println(max(List.<Integer>of()));
    }
}

Learn this lesson with a mentor

Work through the Java + Spring Boot track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now