Java Arena
Step 4 of 5

Saving and loading

save and load

A program that writes its data when it ends, and reads it back when it starts, remembers the data from one run to the next. The data usually lives in an object, so that object's class gets two methods: save writes everything the object holds into a file, and load reads such a file back into the object. The two must agree on the format: load has to understand whatever save writes.

Here's a to-do list. It keeps its tasks in a list of Strings, and its file has one task per line:

Main.java
public class Main {
    public static void main(String[] args) throws Exception {
        TodoList monday = new TodoList();
        monday.add("Water the ferns");
        monday.add("Pay the phone bill");
        monday.save("todo.txt");

        TodoList tuesday = new TodoList();
        tuesday.load("todo.txt");
        tuesday.add("Return the library books");
        tuesday.print();
    }
}
TodoList.java
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Scanner;

public class TodoList {
    private ArrayList<String> tasks;

    public TodoList() {
        this.tasks = new ArrayList<>();
    }

    public void add(String task) {
        this.tasks.add(task);
    }

    public void print() {
        for (int i = 0; i < this.tasks.size(); i++) {
            System.out.println((i + 1) + ". " + this.tasks.get(i));
        }
    }

    public void save(String fileName) throws Exception {
        try (PrintWriter writer = new PrintWriter(fileName)) {
            for (String task : this.tasks) {
                writer.println(task);
            }
        }
    }

    public void load(String fileName) throws Exception {
        try (Scanner reader = new Scanner(Paths.get(fileName))) {
            while (reader.hasNextLine()) {
                String line = reader.nextLine();
                if (!line.isEmpty()) {
                    this.tasks.add(line);
                }
            }
        }
    }
}
Output
1. Water the ferns
2. Pay the phone bill
3. Return the library books
  • tuesday is a new object, and its list starts empty. Its first two tasks come from the file, as they would in a program started again the next day.
  • save writes the whole list every time, with a PrintWriter that replaces what the file held, so the file always matches the list. Appending would put every old task into the file a second time.
  • load adds each line of the file to the list. It skips empty lines, which a person who edits the file by hand may leave behind.
  • Both methods pass their exceptions on with throws Exception. The class can't know what a program wants to do when its file can't be read or written, so the code that calls them decides, as module 42 said.

The first run

The very first time the program runs, there's no todo.txt yet, and load's Scanner throws a NoSuchFileException. For a to-do app that isn't a problem: it just starts with an empty list. So the program catches the exception around load, and goes on:

TodoList list = new TodoList();
try {
    list.load("todo.txt");
} catch (Exception e) {
    System.out.println("No saved tasks yet");
}

Load when it starts, save when it ends

Put together with a loop that reads commands (module 25), this gives a to-do app that remembers its tasks. main loads the list, the loop runs the commands, and after quit, main saves the list:

The file todo.txt
Water the ferns
Pay the phone bill
Main.java
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        TodoList list = new TodoList();
        try {
            list.load("todo.txt");
        } catch (Exception e) {
            System.out.println("No saved tasks yet");
        }

        Scanner scanner = new Scanner(System.in);
        while (true) {
            String command = scanner.nextLine();
            if (command.equals("quit")) {
                break;
            }
            if (command.equals("add")) {
                list.add(scanner.nextLine());
            } else if (command.equals("list")) {
                list.print();
            } else {
                System.out.println("Unknown command: " + command);
            }
        }

        try {
            list.save("todo.txt");
            System.out.println("Saved");
        } catch (Exception e) {
            System.out.println("Could not save: " + e.getMessage());
        }
    }
}
TodoList.java
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Scanner;

public class TodoList {
    private ArrayList<String> tasks;

    public TodoList() {
        this.tasks = new ArrayList<>();
    }

    public void add(String task) {
        this.tasks.add(task);
    }

    public void print() {
        for (int i = 0; i < this.tasks.size(); i++) {
            System.out.println((i + 1) + ". " + this.tasks.get(i));
        }
    }

    public void save(String fileName) throws Exception {
        try (PrintWriter writer = new PrintWriter(fileName)) {
            for (String task : this.tasks) {
                writer.println(task);
            }
        }
    }

    public void load(String fileName) throws Exception {
        try (Scanner reader = new Scanner(Paths.get(fileName))) {
            while (reader.hasNextLine()) {
                String line = reader.nextLine();
                if (!line.isEmpty()) {
                    this.tasks.add(line);
                }
            }
        }
    }
}
Input
add
Buy birdseed
list
quit
Output
1. Water the ferns
2. Pay the phone bill
3. Buy birdseed
Saved

After the run, todo.txt holds the three tasks, and the next time the program starts, it has them all. In a larger program the command loop would be in a user interface class of its own, as in module 25, and main would still load at the start and save at the end.

Your task

Challenge 1 of 3

Step 3's high scores get a class that keeps them. HighScore.java is step 3's class, and ScoreBoard keeps a list of HighScores, with add and best() ready. Write ScoreBoard's methods save(String fileName) and load(String fileName):

  • save writes every score into the file as player,points, one per line, in the list's order, replacing what the file held.
  • load reads such a file, and adds a HighScore for each of its lines to the board.
  • Both pass exceptions on with throws Exception.

The check loads the file shown below, adds Kai's 5100 points, saves the board into the same file and prints the best score:

Kai: 5100

and scores.csv then holds all three scores.

Challenge 2

A program keeps count of how many times it has been run, in the file runs.txt, which holds one number. Write main: it reads the number from runs.txt, adds one, writes the new number back into the file, replacing the old one, and prints Run number and the new number. When there's no runs.txt yet, this is the first run, and the count starts from 0. With the file shown below, the program prints:

Run number 4

and runs.txt then holds 4.

Challenge 3

A wish list app saves its wishes when it ends. The output looks right, but after a few runs the file is full of the same wishes over and over. Find the bug in Wishlist.java and fix it, so that the file holds each wish once. With the file shown below, the program prints:

3 wishes saved

and wishes.txt should then hold kite, bike and board game, one per line.