{"id":"reading-files-objects","slug":"objects-from-a-file","title":"Objects from a file","text":"In the previous step, each line's values were used right away and then forgotten. When the program needs them later, each line becomes an **object**: a class holds one record's values together, and a list holds the objects. Reading the file then takes one loop, and after it the program answers its questions from the list:\n\n```file trails.txt\nCoast Path,14\nPine Loop,6\nRidge Walk,9\n```\n\n```java\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Trail> trails = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(\"trails.txt\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                trails.add(new Trail(parts[0], Integer.valueOf(parts[1])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n\n        System.out.println(\"Trails: \" + trails.size());\n        System.out.println(\"Ten kilometers or less:\");\n        for (Trail trail : trails) {\n            if (trail.getLength() <= 10) {\n                System.out.println(trail);\n            }\n        }\n    }\n}\n// ==== Trail.java ====\npublic class Trail {\n    private String name;\n    private int length;\n\n    public Trail(String name, int length) {\n        this.name = name;\n        this.length = length;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getLength() {\n        return this.length;\n    }\n\n    public String toString() {\n        return this.name + \" (\" + this.length + \" km)\";\n    }\n}\n```\n\n```output\nTrails: 3\nTen kilometers or less:\nPine Loop (6 km)\nRidge Walk (9 km)\n```\n\n- Each line is split, and its parts go to the constructor in the order of its parameters: `new Trail(parts[0], Integer.valueOf(parts[1]))`. The new object goes straight into the list.\n- The list is made before the `try`, so it's still there after it. If the file can't be read, the list simply stays empty.\n- After the loop, the file is done with. The questions (how many, which ones, the longest) are answered from the list, with the tools of module 19.\n\n### Reading in a method\n\nA method that reads the file and returns the list keeps `main` short, and it can read any file of trails:\n\n```java\npublic static ArrayList<Trail> readTrails(String fileName) {\n    ArrayList<Trail> trails = new ArrayList<>();\n    try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n        while (scanner.hasNextLine()) {\n            String[] parts = scanner.nextLine().split(\",\");\n            trails.add(new Trail(parts[0], Integer.valueOf(parts[1])));\n        }\n    } catch (Exception e) {\n        System.out.println(\"Error: \" + e.getMessage());\n    }\n    return trails;\n}\n```\n\n`main` then starts with `ArrayList<Trail> trails = readTrails(\"trails.txt\");`.\n\n### The end of part 4\n\nThis step ends the material of the MOOC's fourth part. Part 4 was the start of object-oriented programming: you wrote classes of your own, created objects from them with constructors, gave them methods such as getters and `toString`, and kept many objects in a list. This module then brought in data from outside the program: files, read line by line and turned into numbers, records and objects. Classes and files together let a program work with as much data as it's given. The MOOC closes the part with [a summary page](https://java-programming.mooc.fi/part-4/4-summary) on its own site, for reading only.\n","task":"The file `plants.txt` has lines such as `fern,7`: a plant, and how many days go by between waterings. `Plant.java` is ready. Complete `main`, so it creates a `Plant` from each line, adds it to the list `plants`, and then prints every plant. For the file shown below, it prints:\n\n```\nfern: water every 7 days\ncactus: water every 21 days\nbasil: water every 2 days\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Plant> plants = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(\"plants.txt\"))) {\n            while (scanner.hasNextLine()) {\n\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n\n    }\n}\n\n// ==== Plant.java ====\npublic class Plant {\n    private String name;\n    private int days;\n\n    public Plant(String name, int days) {\n        this.name = name;\n        this.days = days;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getDays() {\n        return this.days;\n    }\n\n    public String toString() {\n        return this.name + \": water every \" + this.days + \" days\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Plant> plants = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(\"plants.txt\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                plants.add(new Plant(parts[0], Integer.valueOf(parts[1])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n\n        for (Plant plant : plants) {\n            System.out.println(plant);\n        }\n    }\n}\n\n// ==== Plant.java ====\npublic class Plant {\n    private String name;\n    private int days;\n\n    public Plant(String name, int days) {\n        this.name = name;\n        this.days = days;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getDays() {\n        return this.days;\n    }\n\n    public String toString() {\n        return this.name + \": water every \" + this.days + \" days\";\n    }\n}\n","hints":["In the loop, split the line: `String[] parts = scanner.nextLine().split(\",\");`. Then `plants.add(new Plant(parts[0], Integer.valueOf(parts[1])));`.","After the `try`/`catch`, print every plant with a for-each loop. Printing a `Plant` prints what its `toString` returns."],"tests":[{"name":"Test 1","stdin":"","files":{"plants.txt":"fern,7\ncactus,21\nbasil,2\n"},"expect":"fern: water every 7 days\ncactus: water every 21 days\nbasil: water every 2 days","hidden":false},{"name":"Test 2","stdin":"","files":{"plants.txt":"orchid,10\nivy,5\n"},"expect":"orchid: water every 10 days\nivy: water every 5 days","hidden":true},{"name":"Test 3","stdin":"","files":{"plants.txt":"aloe,14\nmint,1\nlavender,6\npalm,4\n"},"expect":"aloe: water every 14 days\nmint: water every 1 days\nlavender: water every 6 days\npalm: water every 4 days","hidden":true}],"require":[{"pattern":"new\\s+Plant\\s*\\(","message":"Create a `Plant` object from each line with `new Plant(...)`."}],"forbid":[],"more":[{"task":"`Recipe.java` is ready. Write a method `readRecipes(String fileName, ArrayList<Recipe> recipes)` that reads a file of lines such as `pancakes,20` (a recipe and the minutes it takes) and adds a `Recipe` for each of them to the list it's given. Some lines are notes with no comma, such as `ask Grandma about the pie`: skip them. The method returns how many lines it skipped. If the file can't be read, it prints `Error: ` and the error's message, and returns 0.\n\nThe check makes an empty list and calls `readRecipes(\"recipes.txt\", recipes)` with the file shown below. It prints what the method returned, the list's size and its first recipe:\n\n```\n1\n3\npancakes (20 min)\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Recipe> recipes = new ArrayList<>();\n        int skipped = readRecipes(\"recipes.txt\", recipes);\n        System.out.println(\"Skipped lines: \" + skipped);\n        for (Recipe recipe : recipes) {\n            System.out.println(recipe);\n        }\n    }\n\n    // Write the readRecipes method here.\n}\n\n// ==== Recipe.java ====\npublic class Recipe {\n    private String name;\n    private int minutes;\n\n    public Recipe(String name, int minutes) {\n        this.name = name;\n        this.minutes = minutes;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getMinutes() {\n        return this.minutes;\n    }\n\n    public String toString() {\n        return this.name + \" (\" + this.minutes + \" min)\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Recipe> recipes = new ArrayList<>();\n        int skipped = readRecipes(\"recipes.txt\", recipes);\n        System.out.println(\"Skipped lines: \" + skipped);\n        for (Recipe recipe : recipes) {\n            System.out.println(recipe);\n        }\n    }\n\n    public static int readRecipes(String fileName, ArrayList<Recipe> recipes) {\n        int skipped = 0;\n        try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                if (parts.length < 2) {\n                    skipped++;\n                    continue;\n                }\n                recipes.add(new Recipe(parts[0], Integer.valueOf(parts[1])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n            return 0;\n        }\n        return skipped;\n    }\n}\n\n// ==== Recipe.java ====\npublic class Recipe {\n    private String name;\n    private int minutes;\n\n    public Recipe(String name, int minutes) {\n        this.name = name;\n        this.minutes = minutes;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getMinutes() {\n        return this.minutes;\n    }\n\n    public String toString() {\n        return this.name + \" (\" + this.minutes + \" min)\";\n    }\n}\n","hints":["The method starts `public static int readRecipes(String fileName, ArrayList<Recipe> recipes) {`. It adds to the caller's list, as in module 14, so it doesn't make a list of its own.","A note has no comma, so it splits into one part: `if (parts.length < 2) { skipped++; continue; }`. Return `skipped` after the `try`/`catch`, and 0 from the `catch` block."],"tests":[{"name":"Reading recipes.txt","stdin":"","call":"ArrayList<Recipe> recipes = new ArrayList<>();\nSystem.out.println(readRecipes(\"recipes.txt\", recipes));\nSystem.out.println(recipes.size());\nSystem.out.println(recipes.get(0));\n","files":{"recipes.txt":"pancakes,20\ntomato soup,45\nask Grandma about the pie\ngreen salad,10\n"},"expect":"1\n3\npancakes (20 min)","hidden":false},{"name":"A list that isn't empty","stdin":"","call":"ArrayList<Recipe> recipes = new ArrayList<>();\nrecipes.add(new Recipe(\"toast\", 5));\nSystem.out.println(readRecipes(\"quick.txt\", recipes));\nSystem.out.println(recipes.size());\nSystem.out.println(recipes.get(1).getName());\nSystem.out.println(recipes.get(1).getMinutes());\n","files":{"recipes.txt":"pancakes,20\n","quick.txt":"omelette,8\nporridge needs oats\nQUICK MEALS\n"},"expect":"2\n2\nomelette\n8","hidden":true},{"name":"No notes","stdin":"","call":"ArrayList<Recipe> recipes = new ArrayList<>();\nSystem.out.println(readRecipes(\"recipes.txt\", recipes));\nSystem.out.println(recipes);\n","files":{"recipes.txt":"curry,50\nrice,15\n"},"expect":"0\n[curry (50 min), rice (15 min)]","hidden":true},{"name":"A missing file","stdin":"","call":"ArrayList<Recipe> recipes = new ArrayList<>();\nSystem.out.println(readRecipes(\"desserts.txt\", recipes));\nSystem.out.println(recipes.size());\n","files":{"recipes.txt":"pancakes,20\n"},"expect":"Error: desserts.txt\n0\n0","hidden":true}],"require":[],"forbid":[]},{"task":"The file `flights.txt` has a line for each flight of the day, such as `AY123,15`: the flight's code and how many minutes late it left. `Flight.java` is ready. Read the flights into a list of `Flight` objects, and then print the number of flights, how many were more than 30 minutes late, and the flight that was the latest. The file has at least one flight; if two share the largest delay, print the first of them. For the file shown below, the program prints:\n\n```\nFlights: 4\nOver 30 minutes late: 1\nLatest: AY904 (45 min late)\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n\n    }\n}\n\n// ==== Flight.java ====\npublic class Flight {\n    private String code;\n    private int delay;\n\n    public Flight(String code, int delay) {\n        this.code = code;\n        this.delay = delay;\n    }\n\n    public String getCode() {\n        return this.code;\n    }\n\n    public int getDelay() {\n        return this.delay;\n    }\n\n    public String toString() {\n        return this.code + \" (\" + this.delay + \" min late)\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Flight> flights = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(\"flights.txt\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                flights.add(new Flight(parts[0], Integer.valueOf(parts[1])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n\n        int veryLate = 0;\n        Flight latest = flights.get(0);\n        for (Flight flight : flights) {\n            if (flight.getDelay() > 30) {\n                veryLate++;\n            }\n            if (flight.getDelay() > latest.getDelay()) {\n                latest = flight;\n            }\n        }\n        System.out.println(\"Flights: \" + flights.size());\n        System.out.println(\"Over 30 minutes late: \" + veryLate);\n        System.out.println(\"Latest: \" + latest);\n    }\n}\n\n// ==== Flight.java ====\npublic class Flight {\n    private String code;\n    private int delay;\n\n    public Flight(String code, int delay) {\n        this.code = code;\n        this.delay = delay;\n    }\n\n    public String getCode() {\n        return this.code;\n    }\n\n    public int getDelay() {\n        return this.delay;\n    }\n\n    public String toString() {\n        return this.code + \" (\" + this.delay + \" min late)\";\n    }\n}\n","hints":["First read the file into an `ArrayList<Flight>`, as in the example above. Then answer the questions from the list, in a for-each loop.","Keep the latest flight in a variable that starts as `flights.get(0)`, and replace it when `flight.getDelay() > latest.getDelay()`. Printing it uses its `toString`."],"tests":[{"name":"Test 1","stdin":"","files":{"flights.txt":"AY123,15\nBA456,0\nAY904,45\nLH777,30\n"},"expect":"Flights: 4\nOver 30 minutes late: 1\nLatest: AY904 (45 min late)","hidden":false},{"name":"Test 2","stdin":"","files":{"flights.txt":"SK100,5\n"},"expect":"Flights: 1\nOver 30 minutes late: 0\nLatest: SK100 (5 min late)","hidden":true},{"name":"Test 3","stdin":"","files":{"flights.txt":"KL1,40\nKL2,55\nKL3,55\nKL4,31\nKL5,2\n"},"expect":"Flights: 5\nOver 30 minutes late: 4\nLatest: KL2 (55 min late)","hidden":true},{"name":"Test 4","stdin":"","files":{"flights.txt":"FR10,0\nFR20,0\n"},"expect":"Flights: 2\nOver 30 minutes late: 0\nLatest: FR10 (0 min late)","hidden":true}],"require":[{"pattern":"new\\s+Flight\\s*\\(","message":"Create a `Flight` object from each line with `new Flight(...)`."}],"forbid":[]}]}