{"id":"writing-files-objects","slug":"lists-of-objects","title":"try-with-resources, and objects in a file","text":"### Closed however the block ends\n\nA 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:\n\n```java\nimport java.io.PrintWriter;\nimport java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        String[] points = {\"120\", \"95\", \"x\", \"80\"};\n        try (PrintWriter writer = new PrintWriter(\"points.txt\")) {\n            for (String point : points) {\n                writer.println(Integer.parseInt(point));\n            }\n        } catch (NumberFormatException e) {\n            System.out.println(\"Stopped: \" + e.getMessage());\n        }\n\n        try (Scanner reader = new Scanner(Paths.get(\"points.txt\"))) {\n            while (reader.hasNextLine()) {\n                System.out.println(\"Saved: \" + reader.nextLine());\n            }\n        }\n    }\n}\n```\n\n```output\nStopped: For input string: \"x\"\nSaved: 120\nSaved: 95\n```\n\n- `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.\n- 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.\n- 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.\n\ntry-with-resources is shorter than calling `close()` yourself, and there's nothing to forget, so from here on this module writes files that way.\n\n### A list of objects, one per line\n\nA 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:\n\n```java\nimport java.io.PrintWriter;\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        ArrayList<HighScore> scores = new ArrayList<>();\n        scores.add(new HighScore(\"Mia\", 4200));\n        scores.add(new HighScore(\"Leo\", 3950));\n        scores.add(new HighScore(\"Ana\", 3100));\n\n        try (PrintWriter writer = new PrintWriter(\"highscores.csv\")) {\n            for (HighScore score : scores) {\n                writer.println(score.toCsv());\n            }\n        }\n        System.out.println(\"Saved \" + scores.size() + \" scores\");\n        System.out.println(scores.get(0));\n    }\n}\n// ==== HighScore.java ====\npublic class HighScore {\n    private String player;\n    private int points;\n\n    public HighScore(String player, int points) {\n        this.player = player;\n        this.points = points;\n    }\n\n    public String getPlayer() {\n        return this.player;\n    }\n\n    public int getPoints() {\n        return this.points;\n    }\n\n    public String toCsv() {\n        return this.player + \",\" + this.points;\n    }\n\n    @Override\n    public String toString() {\n        return this.player + \": \" + this.points;\n    }\n}\n```\n\n```output\nSaved 3 scores\nMia: 4200\n```\n\nAfter the run, `highscores.csv` holds:\n\n```text\nMia,4200\nLeo,3950\nAna,3100\n```\n\n- The loop writes one line per object, in the list's order.\n- `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.\n- `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.\n\n### Headers and commas\n\n- 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.\n- 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).\n","task":"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.\n\nThe check exports the three records of `main` into `grades.csv`, and after it the file should hold:\n\n```text\nname,exam,project,total\nAda,38,45,83\nLinus,29,40,69\nGrace,41,50,91\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.io.PrintWriter;\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        ArrayList<StudentRecord> records = new ArrayList<>();\n        records.add(new StudentRecord(\"Ada\", 38, 45));\n        records.add(new StudentRecord(\"Linus\", 29, 40));\n        records.add(new StudentRecord(\"Grace\", 41, 50));\n        export(records, \"grades.csv\");\n    }\n\n    // Write the method export here.\n}\n\n// ==== StudentRecord.java ====\npublic class StudentRecord {\n    private String name;\n    private int exam;\n    private int project;\n\n    public StudentRecord(String name, int exam, int project) {\n        this.name = name;\n        this.exam = exam;\n        this.project = project;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getExam() {\n        return this.exam;\n    }\n\n    public int getProject() {\n        return this.project;\n    }\n\n    public int total() {\n        return this.exam + this.project;\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.io.PrintWriter;\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        ArrayList<StudentRecord> records = new ArrayList<>();\n        records.add(new StudentRecord(\"Ada\", 38, 45));\n        records.add(new StudentRecord(\"Linus\", 29, 40));\n        records.add(new StudentRecord(\"Grace\", 41, 50));\n        export(records, \"grades.csv\");\n    }\n\n    public static void export(ArrayList<StudentRecord> records, String fileName) throws Exception {\n        try (PrintWriter writer = new PrintWriter(fileName)) {\n            writer.println(\"name,exam,project,total\");\n            for (StudentRecord record : records) {\n                writer.println(record.getName() + \",\" + record.getExam() + \",\" + record.getProject() + \",\" + record.total());\n            }\n        }\n    }\n}\n\n// ==== StudentRecord.java ====\npublic class StudentRecord {\n    private String name;\n    private int exam;\n    private int project;\n\n    public StudentRecord(String name, int exam, int project) {\n        this.name = name;\n        this.exam = exam;\n        this.project = project;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getExam() {\n        return this.exam;\n    }\n\n    public int getProject() {\n        return this.project;\n    }\n\n    public int total() {\n        return this.exam + this.project;\n    }\n}\n","hints":["The header is `public static void export(ArrayList<StudentRecord> records, String fileName) throws Exception {`, and inside it goes `try (PrintWriter writer = new PrintWriter(fileName)) { ... }`.","In the `try` block, `println` the header line once, and then loop over the records: `writer.println(record.getName() + \",\" + record.getExam() + \",\" + record.getProject() + \",\" + record.total());`."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<StudentRecord> records = new ArrayList<>();\nrecords.add(new StudentRecord(\"Ada\", 38, 45));\nrecords.add(new StudentRecord(\"Linus\", 29, 40));\nrecords.add(new StudentRecord(\"Grace\", 41, 50));\nexport(records, \"grades.csv\");\n","writes":{"grades.csv":"name,exam,project,total\nAda,38,45,83\nLinus,29,40,69\nGrace,41,50,91"},"expect":"","hidden":false},{"name":"export(new ArrayList<>(), \"empty.csv\");","stdin":"","call":"export(new ArrayList<>(), \"empty.csv\");\n","writes":{"empty.csv":"name,exam,project,total"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","call":"ArrayList<StudentRecord> records = new ArrayList<>();\nrecords.add(new StudentRecord(\"Mei\", 0, 12));\nrecords.add(new StudentRecord(\"Omar\", 50, 50));\nexport(records, \"spring.csv\");\nrecords.add(new StudentRecord(\"Pia\", 7, 9));\nexport(records, \"spring.csv\");\n","writes":{"spring.csv":"name,exam,project,total\nMei,0,12,12\nOmar,50,50,100\nPia,7,9,16"},"expect":"","hidden":true}],"require":[{"pattern":"\\btry\\s*\\(\\s*(?:PrintWriter|FileWriter)\\b","message":"Write the file in a try-with-resources, for example `try (PrintWriter writer = new PrintWriter(fileName)) {`."}],"forbid":[],"more":[{"task":"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?\n","kind":"predict","seed":"import java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        String[] entries = {\"7\", \"12\", \"many\", \"3\"};\n\n        try {\n            PrintWriter writer = new PrintWriter(\"first.txt\");\n            for (String entry : entries) {\n                writer.println(Integer.parseInt(entry));\n            }\n            writer.close();\n        } catch (NumberFormatException e) {\n            System.out.println(\"first: bad entry\");\n        }\n\n        try (PrintWriter writer = new PrintWriter(\"second.txt\")) {\n            for (String entry : entries) {\n                writer.println(Integer.parseInt(entry));\n            }\n        } catch (NumberFormatException e) {\n            System.out.println(\"second: bad entry\");\n        }\n\n        System.out.println(Files.readAllLines(Paths.get(\"first.txt\")));\n        System.out.println(Files.readAllLines(Paths.get(\"second.txt\")));\n    }\n}\n","solution":"import java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        String[] entries = {\"7\", \"12\", \"many\", \"3\"};\n\n        try {\n            PrintWriter writer = new PrintWriter(\"first.txt\");\n            for (String entry : entries) {\n                writer.println(Integer.parseInt(entry));\n            }\n            writer.close();\n        } catch (NumberFormatException e) {\n            System.out.println(\"first: bad entry\");\n        }\n\n        try (PrintWriter writer = new PrintWriter(\"second.txt\")) {\n            for (String entry : entries) {\n                writer.println(Integer.parseInt(entry));\n            }\n        } catch (NumberFormatException e) {\n            System.out.println(\"second: bad entry\");\n        }\n\n        System.out.println(Files.readAllLines(Paths.get(\"first.txt\")));\n        System.out.println(Files.readAllLines(Paths.get(\"second.txt\")));\n    }\n}\n","hints":["In both, `parseInt(\"many\")` throws in the third round, so `3` is never written, and each `catch` block prints its line.","In the first, the exception skips `writer.close()`, so the text the PrintWriter was holding never reaches `first.txt`, and the list is empty. try-with-resources closes the second writer on the way out, so `second.txt` has the lines written before the problem."],"tests":[{"name":"Output","stdin":"","expect":"first: bad entry\nsecond: bad entry\n[]\n[7, 12]","hidden":false}],"require":[],"forbid":[],"lines":["first: bad entry","second: bad entry","[]","[7, 12]"]},{"task":"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.\n\nOpen 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:\n\n```text\nAino,11\nBen,9\nDara,12\n```\n\nand `seniors.txt`:\n\n```text\nChloe,14\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.io.PrintWriter;\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        ArrayList<Camper> campers = new ArrayList<>();\n        campers.add(new Camper(\"Aino\", 11));\n        campers.add(new Camper(\"Ben\", 9));\n        campers.add(new Camper(\"Chloe\", 14));\n        campers.add(new Camper(\"Dara\", 12));\n        splitGroups(campers);\n    }\n\n    // Write the method splitGroups here.\n}\n\n// ==== Camper.java ====\npublic class Camper {\n    private String name;\n    private int age;\n\n    public Camper(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getAge() {\n        return this.age;\n    }\n\n    @Override\n    public String toString() {\n        return this.name + \" (\" + this.age + \")\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.io.PrintWriter;\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        ArrayList<Camper> campers = new ArrayList<>();\n        campers.add(new Camper(\"Aino\", 11));\n        campers.add(new Camper(\"Ben\", 9));\n        campers.add(new Camper(\"Chloe\", 14));\n        campers.add(new Camper(\"Dara\", 12));\n        splitGroups(campers);\n    }\n\n    public static void splitGroups(ArrayList<Camper> campers) throws Exception {\n        try (PrintWriter juniors = new PrintWriter(\"juniors.txt\"); PrintWriter seniors = new PrintWriter(\"seniors.txt\")) {\n            for (Camper camper : campers) {\n                String line = camper.getName() + \",\" + camper.getAge();\n                if (camper.getAge() <= 12) {\n                    juniors.println(line);\n                } else {\n                    seniors.println(line);\n                }\n            }\n        }\n    }\n}\n\n// ==== Camper.java ====\npublic class Camper {\n    private String name;\n    private int age;\n\n    public Camper(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getAge() {\n        return this.age;\n    }\n\n    @Override\n    public String toString() {\n        return this.name + \" (\" + this.age + \")\";\n    }\n}\n","hints":["`try (PrintWriter juniors = new PrintWriter(\"juniors.txt\"); PrintWriter seniors = new PrintWriter(\"seniors.txt\")) {` creates both files, and closes both when the block ends.","Inside, one loop: when `camper.getAge() <= 12`, the line goes to `juniors`, and otherwise to `seniors`. The line is `camper.getName() + \",\" + camper.getAge()`, not the camper's `toString`."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<Camper> campers = new ArrayList<>();\ncampers.add(new Camper(\"Aino\", 11));\ncampers.add(new Camper(\"Ben\", 9));\ncampers.add(new Camper(\"Chloe\", 14));\ncampers.add(new Camper(\"Dara\", 12));\nsplitGroups(campers);\n","writes":{"juniors.txt":"Aino,11\nBen,9\nDara,12","seniors.txt":"Chloe,14"},"expect":"","hidden":false},{"name":"Test 2","stdin":"","call":"ArrayList<Camper> campers = new ArrayList<>();\ncampers.add(new Camper(\"Eemil\", 15));\ncampers.add(new Camper(\"Fatima\", 13));\nsplitGroups(campers);\n","writes":{"juniors.txt":"","seniors.txt":"Eemil,15\nFatima,13"},"expect":"","hidden":true},{"name":"Test 3","stdin":"","call":"ArrayList<Camper> campers = new ArrayList<>();\ncampers.add(new Camper(\"Gus\", 7));\ncampers.add(new Camper(\"Hana\", 16));\ncampers.add(new Camper(\"Ivo\", 12));\ncampers.add(new Camper(\"Jade\", 13));\ncampers.add(new Camper(\"Kim\", 8));\nsplitGroups(campers);\n","writes":{"juniors.txt":"Gus,7\nIvo,12\nKim,8","seniors.txt":"Hana,16\nJade,13"},"expect":"","hidden":true}],"require":[{"pattern":"\\btry\\s*\\(\\s*(?:PrintWriter|FileWriter)[^{]*;\\s*(?:PrintWriter|FileWriter)\\b","message":"Open both writers in the parentheses of one `try`, separated by a semicolon, for example `try (PrintWriter juniors = ...; PrintWriter seniors = ...) {`."}],"forbid":[]}]}