Before this lesson

Complete Java collections, exceptions and UTF-8 file I/O. Understand accumulation by key.

What you will be able to do

Combine validation, collections and stable sorting while preserving rejected row numbers.

Understand the mechanism

Define the input contract first: a course name and whole minutes separated by one semicolon, with no semicolons inside course names. This is a small project format, not a general CSV parser with quoting and escaping.

Separate parsing, validation, aggregation and presentation. split uses -1 to retain trailing empty fields. Keep original one-based line numbers for rejected rows instead of renumbering after filtering.

Only valid rows contribute to totals. Long values accumulate in a TreeMap for deterministic course-name order. Report rejected rows separately so partial success is visible. These ASCII names do not imply a Chinese collation policy.

Runnable example

Use JDK 21. Save Main.java in a separate practice directory, compile with javac -encoding UTF-8 Main.java, then run java Main. Each lesson is independent; do not combine multiple public Main classes.

import java.util.*;
public class Main {
    public static void main(String[] args) {
        String[] rows = {"Python;25", "SQL;20", "Python;35", "Java;bad", "SQL;-5", ";10"};
        Map<String, Long> totals = new TreeMap<>();
        List<Integer> rejected = new ArrayList<>();
        for (int i = 0; i < rows.length; i++) {
            String[] cells = rows[i].split(";", -1);
            try {
                if (cells.length != 2 || cells[0].isBlank()) throw new IllegalArgumentException();
                int minutes = Integer.parseInt(cells[1].trim());
                if (minutes < 0 || minutes > 240) throw new IllegalArgumentException();
                totals.merge(cells[0].trim(), (long) minutes, Long::sum);
            } catch (IllegalArgumentException e) { rejected.add(i + 1); }
        }
        totals.forEach((name, minutes) -> System.out.println(name + ": " + minutes));
        System.out.println("Rejected lines: " + rejected);
    }
}

Trace and verify

Python totals 60 and SQL totals 20. Rows 4, 5 and 6 have a malformed number, a negative duration and a missing name, producing [4, 5, 6].

Common mistakes and boundaries

Course names are case-sensitive: Python and python are different keys. Define a normalization policy before applying it during aggregation.

Practice challenge

Append Java;0 and Python;240 to check zero and the upper limit, then try SQL;241.

Reference solution and reasoning

Totals become Java 0, Python 300 and SQL 20; new row 9 is rejected. Do not discard a valid course merely because its duration is zero.

Further reading