Memra

Capstone: a small Java program, end to end

Read lines, parse into records, aggregate with streams, handle errors, print a report — every major concept in one program.

The capstone program: Sales Report

This small program brings together nearly every concept in the course: records, streams, try-with-resources file I/O, error handling, static factories, immutability, and clean output.

### What the program does

It reads a CSV file of sales records (one per line: productId,amount,region), parses each line into an immutable SaleRecord, groups them by region using streams, computes total revenue per region, and prints a sorted report.

### Step 1: the data model — a record

Each parsed line becomes a SaleRecord. A record is ideal here: immutable, with equals/hashCode/toString for free.

public record SaleRecord(String productId, double amount, String region) {
    public SaleRecord {
        if (amount < 0) throw new IllegalArgumentException("negative amount: " + amount);
        region = region.trim().toUpperCase();
    }
}

The compact constructor runs before fields are assigned. We validate the amount and normalise the region — both are part of the value's invariant.

### Step 2: reading and parsing — try-with-resources

static List<SaleRecord> loadRecords(String path) throws IOException {
    var records = new ArrayList<SaleRecord>();
    try (var reader = new BufferedReader(new FileReader(path))) {
        String line;
        while ((line = reader.readLine()) != null) {
            try {
                records.add(parse(line));
            } catch (IllegalArgumentException e) {
                System.err.println("Skipping bad line: " + e.getMessage());
            }
        }
    }   // reader.close() called automatically
    return List.copyOf(records);
}

The outer try-with-resources ensures the file is closed even if an exception escapes. The inner try/catch skips corrupt lines without aborting the whole read. List.copyOf returns an unmodifiable snapshot.

### Step 3: aggregation — a stream pipeline

static Map<String, Double> totalsByRegion(List<SaleRecord> records) {
    return records.stream()
        .collect(Collectors.groupingBy(
            SaleRecord::region,
            Collectors.summingDouble(SaleRecord::amount)
        ));
}

groupingBy partitions the stream by key; the downstream collector accumulates each group's amounts into a total. The result is Map<String, Double>.

### Step 4: printing the report — sorted

static void printReport(Map<String, Double> totals) {
    totals.entrySet().stream()
        .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
        .forEach(e -> System.out.printf("%-12s %10.2f%n", e.getKey(), e.getValue()));
}

comparingByValue().reversed() sorts entries from highest to lowest total.

### Concepts woven in

| Section | Course concept | |---|---| | record SaleRecord | records, compact constructor, immutability | | List.copyOf | defensive copy out, unmodifiable collections | | try-with-resources | AutoCloseable, suppressed exceptions, close order | | inner try/catch per line | checked vs unchecked, partial-failure handling | | stream().collect(groupingBy(...)) | Stream API, Collectors, method references | | comparingByValue().reversed() | Comparator chaining, lambda/method reference | | throws IOException | checked exception in method signature |

This is the shape of real Java work: a clear data model, controlled I/O, business logic expressed as a pipeline, and clean output. Every module of this course contributed a piece.

NORMAL ~/memra/learn/java-from-zero/capstone utf-8 LF