Learn Java with Tests

A TDD course for Java, in the spirit of Learn Go with Tests.

Chapter 4 of 18

04 — Lists

List.of, enhanced for-loops, and your first streams refactor.

Kata: Sum.sum(List<Integer>) and Sum.sumAll(List<List<Integer>>) — summing one list, then a list of lists. New ideas: List, List.of, the enhanced for-each loop, and your first streams refactor.

Step 1 — write the test for sum

@Test
void sumsANonEmptyList() {
    assertEquals(15, Sum.sum(List.of(1, 2, 3, 4, 5)));
}

Run it: RED (Sum missing). Then the minimum:

public static int sum(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        total += number;
    }
    return total;
}

GREEN.

Step 2 — List.of is your test fixture literal

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

Note: List.of() null-forbids and produces an immutable list. You can’t append. That’s a feature — it teaches you to reach for streams and toList() rather than mutating.

Step 3 — sumAll: compose the function you already have

A function summing a list of lists:

public static int sumAll(List<List<Integer>> numbersLists) {
    int total = 0;
    for (List<Integer> numbers : numbersLists) {
        total += sum(numbers);
    }
    return total;
}

Drive it with tests. List.of(List.of(1, 2), List.of(0, 9), List.of(3))15. The enhanced for-each loop composes — calling the sum you already have.

REFACTOR — the streams kick-off

Now the big refactor. Your loop can collapse into a pipeline:

public static int sum(List<Integer> numbers) {
    return numbers.stream().mapToInt(Integer::intValue).sum();
}

Same test, same answer, expression-based instead of statement-based. State (total), mutation, and the loop disappear — the compiler reasons about it for you.

Don’t refactor this away yet if it feels abstract. Chapter 08 makes you fluent in streams. What matters now: when you see a collection, the first thought for “reduce to one value” should become a stream pipeline.

Java-specific notes

  • int total = 0 is Integer boxed/unboxed automatically — List<Integer> stores objects.
  • mapToInt(...) avoids boxing; it turns the stream into a primitive IntStream.
  • List.of() (Java 9+) is the standard fixture literal. Prefer it over Arrays.asList or hand-rolled new ArrayList<>(...) in tests.

Run it

gradle test --tests "collections.SumTest"

Key takeaway: generics give you a List<Integer> whose element type is proven at compile time. And the collection → stream refactor is a habit modern Java codebases expect you to have.

Next: 05 — structs — records and geometry, where TDD meets data types.