Memra

Capstone: a small Java program, end to end

◈ 5 cards

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

SectionCourse concept
record SaleRecordrecords, compact constructor, immutability
List.copyOfdefensive copy out, unmodifiable collections
try-with-resourcesAutoCloseable, suppressed exceptions, close order
inner try/catch per linechecked vs unchecked, partial-failure handling
stream().collect(groupingBy(...))Stream API, Collectors, method references
comparingByValue().reversed()Comparator chaining, lambda/method reference
throws IOExceptionchecked 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.

linesList<SaleRecord>groupstotalssales.csvproductId,amount,regionparse→ SaleRecordgroupingBySaleRecord::regionsummingDoubleMap<String, Double>printReportcomparingByValue().reversed()
Each stage is one concept from the course: a record for the data model, controlled I/O for the read, a collector pair for the aggregation, and a comparator for the output order.
NORMAL ~/memra/learn/java-from-zero/capstone utf-8 LF