Chapter 08 · Reps

Collections, Generics, and Midterm Review — Reps

← Back to Chapter 8

Chapter 8 — Reps (Combination Drills + Midterm Prep)

Conditioning, not grading. AI is OFF. These reps mix concepts across Chapters 1–7. They are deliberately closer in shape to the midterm than to the chapter-specific reps you’ve done so far. If a rep takes you more than 30 minutes, that’s the rep — diagnose what’s slow, then re-do it from scratch.


Rep 1 — List vs Set vs Map Triage

Without writing code, decide for each scenario which collection type to reach for and which implementation. One sentence each.

  1. Counting the occurrences of each word in a paragraph.
  2. Storing the unique authors of a catechism collection.
  3. Tracking the order in which players joined a roster.
  4. Looking up an account by its ID.
  5. Printing the authors alphabetically.
  6. Maintaining the last 5 commands typed at a prompt.
  7. Recording every event with its timestamp, iterated in event order.

Write your answers in a Rep1.txt. Then check yourself: there’s no single right answer for every scenario, but there is a cleanest answer.


Rep 2 — From CSV to Map

Create data/authors.csv:

name,tradition
C. S. Lewis,Anglican
G. K. Chesterton,Roman
Martin Luther,Lutheran
John Calvin,Reformed
N. T. Wright,Anglican

Then Rep2.java:

import java.nio.file.*;
import java.util.*;

public class Rep2 {
    public static void main(String[] args) throws Exception {
        List<String> lines = Files.readAllLines(Path.of("data/authors.csv"));
        Map<String, List<String>> byTradition = new TreeMap<>();   // sorted by tradition
        for (int i = 1; i < lines.size(); i++) {                   // skip header
            String[] fields = lines.get(i).split(",");
            String name = fields[0].trim();
            String tradition = fields[1].trim();
            byTradition.computeIfAbsent(tradition, k -> new ArrayList<>()).add(name);
        }
        for (var entry : byTradition.entrySet()) {
            System.out.println(entry.getKey() + ":");
            for (String name : entry.getValue()) {
                System.out.println("  " + name);
            }
        }
    }
}

Run. Note the output is grouped by tradition (sorted alphabetically thanks to TreeMap). Three concepts at once: file reading, parsing, and the right collection choice.

computeIfAbsent is the modern Java idiom for “if the key isn’t there, put this default; either way, return the value.” It saves you a containsKey check.


Rep 3 — Spec + Test + Code

A short three-part rep. Build a Roster class.

Part 1. Write the Javadoc spec for Roster — three methods: add(name) (adds if not present), remove(name) (removes if present), contains(name) (boolean). State the postconditions explicitly. Save as Roster.spec.txt.

Part 2. Write three JUnit tests against the spec. They should fail (because the class doesn’t exist yet). Save as RosterTest.java.

Part 3. Implement Roster.java using a LinkedHashSet<String> underneath. Make the tests pass.

Spec → Test → Code. Coding 2’s spine pattern. Twenty minutes total if you stay focused.


Rep 4 — Validating Record

Create Rep4.java:

public class Rep4 {
    public record Resource(String title, String author, int year, String tradition) {
        public Resource {
            if (title == null || title.isBlank()) throw new IllegalArgumentException("title");
            if (author == null || author.isBlank()) throw new IllegalArgumentException("author");
            if (year < 0) throw new IllegalArgumentException("year");
            // Tradition: allow only specific values
            switch (tradition) {
                case "Lutheran", "Reformed", "Roman", "Anglican", "Evangelical" -> {}
                default -> throw new IllegalArgumentException("unknown tradition: " + tradition);
            }
        }
    }

    public static void main(String[] args) {
        Resource ok = new Resource("Mere Christianity", "C. S. Lewis", 1952, "Anglican");
        System.out.println("OK: " + ok);

        for (Object[] bad : new Object[][]{
            {"", "Author", 2000, "Lutheran"},
            {"Title", "Author", -1, "Lutheran"},
            {"Title", "Author", 2000, "Made up"},
            {null, "Author", 2000, "Lutheran"}
        }) {
            try {
                new Resource((String) bad[0], (String) bad[1], (int) bad[2], (String) bad[3]);
                System.out.println("FAIL — accepted: " + java.util.Arrays.toString(bad));
            } catch (IllegalArgumentException ex) {
                System.out.println("OK rejected: " + java.util.Arrays.toString(bad) + " — " + ex.getMessage());
            }
        }
    }
}

Run. All five lines should print as expected. Records + compact constructors + switch expression: three Java 14+/16+ features rolled into one defense.


Rep 5 — Build a Tiny Catalog

Combine Rep 2’s parsing + Rep 4’s validating record + Rep 3’s spec/test discipline. Create Catalog.java:

import java.nio.file.*;
import java.util.*;

public class Catalog {
    public record Resource(String title, String author, String tradition) {
        public Resource {
            if (title == null || title.isBlank()) throw new IllegalArgumentException("title");
            if (author == null || author.isBlank()) throw new IllegalArgumentException("author");
            if (tradition == null || tradition.isBlank()) throw new IllegalArgumentException("tradition");
        }
    }

    private final List<Resource> resources = new ArrayList<>();
    private final Map<String, List<Resource>> byAuthor = new HashMap<>();
    private final Map<String, List<Resource>> byTradition = new HashMap<>();

    public void add(Resource r) {
        resources.add(r);
        byAuthor.computeIfAbsent(r.author(), k -> new ArrayList<>()).add(r);
        byTradition.computeIfAbsent(r.tradition(), k -> new ArrayList<>()).add(r);
    }

    public List<Resource> findByAuthor(String author) {
        return byAuthor.getOrDefault(author, List.of());
    }

    public List<Resource> findByTradition(String tradition) {
        return byTradition.getOrDefault(tradition, List.of());
    }

    public Set<String> uniqueAuthors() {
        return new TreeSet<>(byAuthor.keySet());      // sorted
    }

    public int size() { return resources.size(); }
}

Then CatalogDemo.java:

public class CatalogDemo {
    public static void main(String[] args) {
        Catalog c = new Catalog();
        c.add(new Catalog.Resource("Mere Christianity", "C. S. Lewis", "Anglican"));
        c.add(new Catalog.Resource("The Great Divorce", "C. S. Lewis", "Anglican"));
        c.add(new Catalog.Resource("Orthodoxy", "G. K. Chesterton", "Roman"));
        c.add(new Catalog.Resource("Small Catechism", "Martin Luther", "Lutheran"));

        System.out.println("Total: " + c.size());
        System.out.println("Authors (sorted):");
        for (String a : c.uniqueAuthors()) System.out.println("  " + a);
        System.out.println("\nAnglican resources:");
        for (var r : c.findByTradition("Anglican")) System.out.println("  " + r.title());
    }
}

Run. This is the shape of the midterm in miniature — record, multiple maps for different indices, query methods. If you can write this from scratch in 20 minutes, you have the spine of the midterm.


Rep 6 — Add Persistence

Extend Rep 5’s Catalog with two new methods:

public static Catalog loadCsv(Path csv) throws IOException { ... }
public void saveCsv(Path csv) throws IOException { ... }

Use SimpleCsv.parseLine (Chapter 6) to parse safely. Use the atomic-write pattern (Chapter 6) to persist safely. Add a test that round-trips: build a catalog → save → load into a new catalog → confirm equality of contents.

This rep combines Chapters 2 (spec), 3 (exceptions on bad file), 4 (test), 6 (file, atomic write), and 8 (collections). It is the cleanest cross-chapter rep in the book.


Rep 7 — Generic Box

Create Box.java:

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

class BoxDemo {
    public static void main(String[] args) {
        Box<String> s = new Box<>("hello");
        Box<Integer> i = new Box<>(42);
        System.out.println(s.get() + " " + i.get());
    }
}

Compile, run. Note: you typed one class definition, used it for two different types. That’s generics.

Now write a generic method:

public static <T> T firstOrDefault(java.util.List<T> items, T fallback) {
    return items.isEmpty() ? fallback : items.get(0);
}

Call it twice — once with List<String>, once with List<Integer>. Note the compiler infers T each time.


Rep 8 — Iteration Without Throwing

Create Rep8.java:

import java.util.*;

public class Rep8 {
    public static void main(String[] args) {
        List<String> roster = new ArrayList<>(List.of("Maya", "Marcus", "Lin", "Xanthe"));

        // BAD: this throws ConcurrentModificationException
        // for (String s : roster) {
        //     if (s.startsWith("X")) roster.remove(s);
        // }

        // GOOD #1: removeIf
        roster.removeIf(s -> s.startsWith("X"));
        System.out.println(roster);
    }
}

Run. Confirm “Xanthe” was removed cleanly. Then uncomment the BAD block and re-run; observe the ConcurrentModificationException. Switch back to the GOOD block.

removeIf is the modern way. Use it.


Rep 9 — Combination Drill: 30-Minute Mini Catalog

A timed rep. Set a 30-minute timer.

Build:

  • A Book record with title, author, year.
  • A Library class with: add(book), findByAuthor(author) -> List<Book>, booksBefore(year) -> List<Book>, uniqueAuthors() -> Set<String> (sorted), size().
  • A main that adds 5 hardcoded books, then prints all unique authors and all books written before 1980.
  • One JUnit test that confirms findByAuthor returns the right books.

Allowed: textbook, your past reps. Not allowed: AI.

Stop the timer. How did you do? If you ran over, identify what slowed you down. That’s the gap. Drill it.

This rep is the closest you’ll come to the midterm before the midterm itself.


Rep 10 — Bug-Hunt Sprint

Each of these methods has one bug. Find each. Fix each. (Aim: 10 minutes total.)

// 1
public static int max(int[] xs) {
    int m = 0;
    for (int x : xs) if (x > m) m = x;
    return m;
}

// 2
public static boolean contains(List<String> list, String target) {
    for (String s : list) {
        if (s == target) return true;
    }
    return false;
}

// 3
public static List<Integer> doubleAll(List<Integer> xs) {
    for (int i = 0; i < xs.size(); i++) {
        xs.set(i, xs.get(i) * 2);
    }
    return xs;
}

// 4
public static int sumDivisibleByThree(int[] xs) {
    int total = 0;
    for (int i = 1; i <= xs.length; i++) {
        if (xs[i] % 3 == 0) total += xs[i];
    }
    return total;
}

// 5
public static String repeat(String s, int n) {
    String result = "";
    for (int i = 0; i < n; i++) result += s;
    return result;            // (not a bug per se, but inefficient — flag it)
}

Hints (don’t read until you’ve tried):

  1. What if all inputs are negative? Initial value should be the first element, not 0.
  2. == on String — use .equals().
  3. Mutates the caller’s list — questionable. Either document or return a new list.
  4. Off-by-one — loop should start at 0 and end at < xs.length.
  5. String concat in a loop is O(n²). Use StringBuilder.

Rep 11 — Mock Midterm (Light)

Set a 45-minute timer. (Not 60 — we’ll save the full 60 for the sample midterm prompt.) Build:

A PrayerJournal class that:

  • Stores entries (date as String like "2026-04-12", topic as String, text as String).
  • Loads from a CSV file (data/journal.csv — make up your own 5-line sample).
  • Provides findByTopic(topic) -> List<Entry>, findByDate(date) -> List<Entry>, topics() -> Set<String> (sorted).
  • Throws IllegalArgumentException on invalid entries (any blank field; date not in YYYY-MM-DD format — naively, length 10 with dashes at index 4 and 7).
  • One JUnit test exercising findByTopic round-trip from a CSV.

Stop the timer. Honest self-assessment: was this Normal-tier-clean in 45 minutes? If yes, you’re ready. If no, identify the slowdown and drill it tomorrow.


Done? One Last Thing.

Open the sample midterm prompt. Read the prompt at the top. Close the file. Open a fresh project. Set a 60-minute timer.

Solve from scratch. AI off. Internet off. Textbook open.

When the timer ends, stop typing. Then open the sample solution (Hymnal.java, Hymn.java, SimpleCsv.java, HymnalSelfCheck.java) and compare honestly.

This is the rep that matters. The students who do this end-to-end before the exam tend to outperform their own expectations. The ones who don’t tend to be surprised by what 60 minutes feels like under pressure.


Up next: Project 8 — Project 8: Apologetics Catalog (MIDTERM).