Java Arena
Step 3 of 5

try-with-resources, and objects in a file

Closed however the block ends

A PrintWriter is a resource like the Scanner that reads a file: its class implements AutoCloseable, so it can go in the parentheses of a try-with-resources (module 42), which closes it as soon as the block ends. That matters when something goes wrong halfway. Here a game saves its players' points from text, and one of them isn't a number:

import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        String[] points = {"120", "95", "x", "80"};
        try (PrintWriter writer = new PrintWriter("points.txt")) {
            for (String point : points) {
                writer.println(Integer.parseInt(point));
            }
        } catch (NumberFormatException e) {
            System.out.println("Stopped: " + e.getMessage());
        }

        try (Scanner reader = new Scanner(Paths.get("points.txt"))) {
            while (reader.hasNextLine()) {
                System.out.println("Saved: " + reader.nextLine());
            }
        }
    }
}
Output
Stopped: For input string: "x"
Saved: 120
Saved: 95
  • parseInt("x") throws, and the loop stops there. The writer is closed on the way out of the try block, before the catch block runs, so the two lines written before the problem reach the file.
  • Written as in step 1, with writer.close() as the last line of the try block, the exception would skip close(), and the file would stay empty.
  • In a method that passes exceptions on with throws Exception, a try-with-resources needs no catch: the writer is closed, and then the exception goes on to the caller.

try-with-resources is shorter than calling close() yourself, and there's nothing to forget, so from here on this module writes files that way.

A list of objects, one per line

A game keeps a high-score table: a list of HighScore objects, each with a player and points. To save it, the program writes one line per object, with the values separated by commas, like the CSV files module 20 read:

Main.java
import java.io.PrintWriter;
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) throws Exception {
        ArrayList<HighScore> scores = new ArrayList<>();
        scores.add(new HighScore("Mia", 4200));
        scores.add(new HighScore("Leo", 3950));
        scores.add(new HighScore("Ana", 3100));

        try (PrintWriter writer = new PrintWriter("highscores.csv")) {
            for (HighScore score : scores) {
                writer.println(score.toCsv());
            }
        }
        System.out.println("Saved " + scores.size() + " scores");
        System.out.println(scores.get(0));
    }
}
HighScore.java
public class HighScore {
    private String player;
    private int points;

    public HighScore(String player, int points) {
        this.player = player;
        this.points = points;
    }

    public String getPlayer() {
        return this.player;
    }

    public int getPoints() {
        return this.points;
    }

    public String toCsv() {
        return this.player + "," + this.points;
    }

    @Override
    public String toString() {
        return this.player + ": " + this.points;
    }
}
Output
Saved 3 scores
Mia: 4200

After the run, highscores.csv holds:

Mia,4200
Leo,3950
Ana,3100
  • The loop writes one line per object, in the list's order.
  • toCsv() says how one HighScore looks as a line of the file. The format is then written in one place: the class knows its own values, and every program that saves HighScores writes the same lines.
  • toString() is for people: it's what println(scores.get(0)) showed on the screen. toCsv() is for the program that reads the file back. writer.println(score) would write Mia: 4200 into the file, which is harder to take apart again. Mia,4200 splits with module 20's split(",") into the player and the points, and Integer.valueOf turns the points back into a number. Step 4 does exactly that.

Headers and commas

  • A file meant for a spreadsheet often starts with a header line that names the columns, such as player,points (module 20 skipped such a line when reading). Write it once, before the loop.
  • A value with a comma of its own, such as the player Lee, Jr., would split into too many parts when the file is read back. Choose a separator that doesn't occur in the data (; is common, as module 20 said), or refuse such values when an object is created (module 42).

Your task

Challenge 1 of 3

A teacher exports a course's gradebook for a spreadsheet. StudentRecord.java is ready: a record has the student's name and the points of the exam and of the project, and total() returns their sum. In Main.java, write the method export(ArrayList<StudentRecord> records, String fileName), which writes the file: first the header line name,exam,project,total, and then one line for each record, in the list's order, with its four values separated by commas. Write the file in a try-with-resources, and let the method pass exceptions on with throws Exception. It prints nothing.

The check exports the three records of main into grades.csv, and after it the file should hold:

name,exam,project,total
Ada,38,45,83
Linus,29,40,69
Grace,41,50,91

Challenge 2

Two ways to write the same numbers, and one of the entries isn't a number. Files.readAllLines (module 42) returns a file's lines in a list. What does the program print?

Challenge 3

A summer camp splits its campers into two groups. Camper.java is ready: a camper has a name and an age. Write the method splitGroups(ArrayList<Camper> campers), which writes every camper aged 12 or under into juniors.txt, and every older camper into seniors.txt, one per line as name,age, in the list's order. Both files are always written, even when a group is empty.

Open both writers in one try-with-resources: its parentheses can hold several resources, separated by a semicolon (module 42). The method prints nothing, and passes exceptions on with throws Exception. For the campers of main, juniors.txt should hold:

Aino,11
Ben,9
Dara,12

and seniors.txt:

Chloe,14