{"id":"streams-objects","slug":"objects","title":"Streams of objects","text":"Streams work on lists of objects too, and that's where they're most useful. The lambdas call the objects' methods: a filter asks about a getter, and `map` turns each object into one of its values.\n\nThe cycling club from step 1 now keeps each ride as an object, with the rider's name and the distance:\n\n```java\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Ride> rides = new ArrayList<>();\n        rides.add(new Ride(\"Noor\", 42));\n        rides.add(new Ride(\"Pablo\", 65));\n        rides.add(new Ride(\"Noor\", 18));\n        rides.add(new Ride(\"Kaia\", 80));\n        rides.add(new Ride(\"Pablo\", 51));\n\n        rides.stream()\n                .filter(ride -> ride.getKm() > 50)\n                .forEach(System.out::println);\n    }\n}\n// ==== Ride.java ====\npublic class Ride {\n    private String rider;\n    private int km;\n\n    public Ride(String rider, int km) {\n        this.rider = rider;\n        this.km = km;\n    }\n\n    public String getRider() {\n        return this.rider;\n    }\n\n    public int getKm() {\n        return this.km;\n    }\n\n    @Override\n    public String toString() {\n        return this.rider + \": \" + this.km + \" km\";\n    }\n}\n```\n\n```output\nPablo: 65 km\nKaia: 80 km\nPablo: 51 km\n```\n\nThe stream is a stream of Ride objects, so `ride` in the lambda is a Ride, and the lambda can call its methods. `forEach(System.out::println)` prints each ride with its `toString`.\n\n### From objects to values\n\n`map` can turn each ride into one of its values. The names of the club's riders, each once, in alphabetical order:\n\n```java\nList<String> riders = rides.stream()\n        .map(ride -> ride.getRider())\n        .distinct()\n        .sorted()\n        .collect(Collectors.toList());\nSystem.out.println(riders);\n```\n\nFor the five rides above, this prints `[Kaia, Noor, Pablo]`. After `map`, the stream holds Strings, so `distinct` and `sorted` work on the names.\n\nA getter works as a method reference too: `Ride::getRider` is short for `ride -> ride.getRider()`, just as `String::toUpperCase` was a method of the value itself, named by its class.\n\n### Sums and averages of a value\n\n`mapToInt` with a getter turns the rides into their distances, as plain `int` values, ready for `sum` and `average`:\n\n```java\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Ride> rides = new ArrayList<>();\n        rides.add(new Ride(\"Noor\", 42));\n        rides.add(new Ride(\"Pablo\", 65));\n        rides.add(new Ride(\"Noor\", 18));\n        rides.add(new Ride(\"Kaia\", 80));\n        rides.add(new Ride(\"Pablo\", 51));\n\n        double average = rides.stream()\n                .mapToInt(Ride::getKm)\n                .average()\n                .getAsDouble();\n        int noorsTotal = rides.stream()\n                .filter(ride -> ride.getRider().equals(\"Noor\"))\n                .mapToInt(Ride::getKm)\n                .sum();\n        System.out.println(\"Average ride: \" + average + \" km\");\n        System.out.println(\"Noor: \" + noorsTotal + \" km\");\n    }\n}\n// ==== Ride.java ====\npublic class Ride {\n    private String rider;\n    private int km;\n\n    public Ride(String rider, int km) {\n        this.rider = rider;\n        this.km = km;\n    }\n\n    public String getRider() {\n        return this.rider;\n    }\n\n    public int getKm() {\n        return this.km;\n    }\n\n    @Override\n    public String toString() {\n        return this.rider + \": \" + this.km + \" km\";\n    }\n}\n```\n\n```output\nAverage ride: 51.2 km\nNoor: 60 km\n```\n\n- The average is 256 / 5 = 51.2.\n- The filter compares names with `equals`, as always for Strings, and then only Noor's rides reach `mapToInt`: 42 + 18 = 60.\n\n### Lists of objects\n\n`collect` works for objects as it does for numbers. `rides.stream().filter(ride -> ride.getKm() > 50).collect(Collectors.toList())` gives a `List<Ride>` of the long rides. The new list holds the same Ride objects as the old one, not copies (module 22): a change made to a ride through one list shows through the other.\n\nOne thing doesn't work yet: `sorted()` on a stream of Rides. Java knows how to order numbers and Strings, but not rides: by distance, or by name? Module 38 shows how to tell it. Until then, map the objects to values first, and sort those.\n","task":"`Ride.java` is ready. In `Main.java`, write the method `totalKm(List<Ride> rides, String rider)`, which returns the total distance of the given rider's rides, as an `int`, and 0 if the rider has none. Use a stream. The check runs:\n\n```java\nList<Ride> rides = new ArrayList<>();\nrides.add(new Ride(\"Noor\", 42));\nrides.add(new Ride(\"Pablo\", 65));\nrides.add(new Ride(\"Noor\", 18));\nSystem.out.println(totalKm(rides, \"Noor\"));\n```\n\nand expects:\n\n```\n60\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Ride> rides = new ArrayList<>();\n        rides.add(new Ride(\"Noor\", 42));\n        rides.add(new Ride(\"Pablo\", 65));\n        rides.add(new Ride(\"Noor\", 18));\n        System.out.println(totalKm(rides, \"Noor\"));\n    }\n\n    // Write the method totalKm here.\n}\n\n// ==== Ride.java ====\npublic class Ride {\n    private String rider;\n    private int km;\n\n    public Ride(String rider, int km) {\n        this.rider = rider;\n        this.km = km;\n    }\n\n    public String getRider() {\n        return this.rider;\n    }\n\n    public int getKm() {\n        return this.km;\n    }\n\n    @Override\n    public String toString() {\n        return this.rider + \": \" + this.km + \" km\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Ride> rides = new ArrayList<>();\n        rides.add(new Ride(\"Noor\", 42));\n        rides.add(new Ride(\"Pablo\", 65));\n        rides.add(new Ride(\"Noor\", 18));\n        System.out.println(totalKm(rides, \"Noor\"));\n    }\n\n    public static int totalKm(List<Ride> rides, String rider) {\n        return rides.stream()\n                .filter(ride -> ride.getRider().equals(rider))\n                .mapToInt(Ride::getKm)\n                .sum();\n    }\n}\n\n// ==== Ride.java ====\npublic class Ride {\n    private String rider;\n    private int km;\n\n    public Ride(String rider, int km) {\n        this.rider = rider;\n        this.km = km;\n    }\n\n    public String getRider() {\n        return this.rider;\n    }\n\n    public int getKm() {\n        return this.km;\n    }\n\n    @Override\n    public String toString() {\n        return this.rider + \": \" + this.km + \" km\";\n    }\n}\n","hints":["The header is `public static int totalKm(List<Ride> rides, String rider)`. Keep the given rider's rides with a filter that compares `ride.getRider()` with `rider` using `equals`.","After the filter, `mapToInt(Ride::getKm)` and `sum()`. The sum of an empty stream is 0."],"tests":[{"name":"Test 1","stdin":"","call":"List<Ride> rides = new ArrayList<>();\nrides.add(new Ride(\"Noor\", 42));\nrides.add(new Ride(\"Pablo\", 65));\nrides.add(new Ride(\"Noor\", 18));\nSystem.out.println(totalKm(rides, \"Noor\"));\n","expect":"60","hidden":false},{"name":"Test 2","stdin":"","call":"List<Ride> rides = new ArrayList<>();\nrides.add(new Ride(\"Kaia\", 80));\nrides.add(new Ride(\"Kai\", 12));\nrides.add(new Ride(\"Kaia\", 5));\nSystem.out.println(totalKm(rides, \"Kai\") + \" \" + totalKm(rides, \"Kaia\"));\nSystem.out.println(totalKm(rides, \"Noor\") + \" \" + totalKm(new ArrayList<>(), \"Kaia\"));\n","expect":"12 85\n0 0","hidden":true},{"name":"Test 3","stdin":"","call":"List<Ride> rides = new ArrayList<>();\nrides.add(new Ride(\"Ines\", 30));\nString name = new String(\"Ines\");\nSystem.out.println(totalKm(rides, name));\n","expect":"30","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+int\\s+totalKm\\s*\\(\\s*List<Ride>\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*\\)","message":"Write the method as `public static int totalKm(List<Ride> rides, String rider)`."},{"pattern":"\\.mapToInt\\s*\\(","message":"Turn the rides into their distances with `mapToInt`."}],"forbid":[{"pattern":"\\b(for|while)\\b","message":"Use a stream this time, not a loop."}],"more":[{"task":"What does this program print?\n","kind":"predict","seed":"import java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Ride> rides = new ArrayList<>();\n        rides.add(new Ride(\"Kaia\", 30));\n        rides.add(new Ride(\"Noor\", 75));\n        rides.add(new Ride(\"Kaia\", 90));\n        rides.add(new Ride(\"Pablo\", 20));\n\n        rides.stream()\n                .filter(ride -> ride.getKm() >= 30)\n                .map(Ride::getRider)\n                .distinct()\n                .forEach(System.out::println);\n        int total = rides.stream()\n                .filter(ride -> ride.getRider().startsWith(\"K\"))\n                .mapToInt(ride -> ride.getKm() / 10)\n                .sum();\n        System.out.println(total);\n    }\n}\n// ==== Ride.java ====\npublic class Ride {\n    private String rider;\n    private int km;\n\n    public Ride(String rider, int km) {\n        this.rider = rider;\n        this.km = km;\n    }\n\n    public String getRider() {\n        return this.rider;\n    }\n\n    public int getKm() {\n        return this.km;\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Ride> rides = new ArrayList<>();\n        rides.add(new Ride(\"Kaia\", 30));\n        rides.add(new Ride(\"Noor\", 75));\n        rides.add(new Ride(\"Kaia\", 90));\n        rides.add(new Ride(\"Pablo\", 20));\n\n        rides.stream()\n                .filter(ride -> ride.getKm() >= 30)\n                .map(Ride::getRider)\n                .distinct()\n                .forEach(System.out::println);\n        int total = rides.stream()\n                .filter(ride -> ride.getRider().startsWith(\"K\"))\n                .mapToInt(ride -> ride.getKm() / 10)\n                .sum();\n        System.out.println(total);\n    }\n}\n// ==== Ride.java ====\npublic class Ride {\n    private String rider;\n    private int km;\n\n    public Ride(String rider, int km) {\n        this.rider = rider;\n        this.km = km;\n    }\n\n    public String getRider() {\n        return this.rider;\n    }\n\n    public int getKm() {\n        return this.km;\n    }\n}\n","hints":["`>=` lets a ride of exactly 30 km through. `distinct` keeps the first copy of each name, in the list's order, and nothing sorts them. In the second stream, each distance is divided by 10 as an `int` before the sum.","Kaia's 30, Noor's 75 and Kaia's 90 get through the first filter, `map` turns them into Kaia, Noor and Kaia, and `distinct` drops the second Kaia. Kaia's are the only rides by a name that starts with K, and `mapToInt` gives 3 and 9 for them."],"tests":[{"name":"Output","stdin":"","expect":"Kaia\nNoor\n12","hidden":false}],"require":[],"forbid":[],"lines":["Kaia","Noor","12"]},{"task":"`Screening.java` is ready: a screening has a film, the number of tickets sold and the number of seats, and `isSoldOut()` says whether every seat is sold. In `Main.java`, write the method `soldOutFilms(List<Screening> screenings)`, which returns a new list of the films that had at least one sold-out screening, each once, in alphabetical order. Use a stream, and `Screening::isSoldOut` in the filter. The check runs:\n\n```java\nList<Screening> screenings = new ArrayList<>();\nscreenings.add(new Screening(\"Vertigo\", 120, 120));\nscreenings.add(new Screening(\"Heat\", 64, 90));\nscreenings.add(new Screening(\"Alien\", 90, 90));\nscreenings.add(new Screening(\"Vertigo\", 80, 80));\nSystem.out.println(soldOutFilms(screenings));\n```\n\nand expects:\n\n```\n[Alien, Vertigo]\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Screening> screenings = new ArrayList<>();\n        screenings.add(new Screening(\"Vertigo\", 120, 120));\n        screenings.add(new Screening(\"Heat\", 64, 90));\n        screenings.add(new Screening(\"Alien\", 90, 90));\n        screenings.add(new Screening(\"Vertigo\", 80, 80));\n        System.out.println(soldOutFilms(screenings));\n    }\n\n    // Write the method soldOutFilms here.\n}\n\n// ==== Screening.java ====\npublic class Screening {\n    private String film;\n    private int sold;\n    private int seats;\n\n    public Screening(String film, int sold, int seats) {\n        this.film = film;\n        this.sold = sold;\n        this.seats = seats;\n    }\n\n    public String getFilm() {\n        return this.film;\n    }\n\n    public int getSold() {\n        return this.sold;\n    }\n\n    public boolean isSoldOut() {\n        return this.sold >= this.seats;\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<Screening> screenings = new ArrayList<>();\n        screenings.add(new Screening(\"Vertigo\", 120, 120));\n        screenings.add(new Screening(\"Heat\", 64, 90));\n        screenings.add(new Screening(\"Alien\", 90, 90));\n        screenings.add(new Screening(\"Vertigo\", 80, 80));\n        System.out.println(soldOutFilms(screenings));\n    }\n\n    public static List<String> soldOutFilms(List<Screening> screenings) {\n        return screenings.stream()\n                .filter(Screening::isSoldOut)\n                .map(Screening::getFilm)\n                .distinct()\n                .sorted()\n                .collect(Collectors.toList());\n    }\n}\n\n// ==== Screening.java ====\npublic class Screening {\n    private String film;\n    private int sold;\n    private int seats;\n\n    public Screening(String film, int sold, int seats) {\n        this.film = film;\n        this.sold = sold;\n        this.seats = seats;\n    }\n\n    public String getFilm() {\n        return this.film;\n    }\n\n    public int getSold() {\n        return this.sold;\n    }\n\n    public boolean isSoldOut() {\n        return this.sold >= this.seats;\n    }\n}\n","hints":["The header is `public static List<String> soldOutFilms(List<Screening> screenings)`. `filter(Screening::isSoldOut)` keeps the sold-out screenings: the method returns the `true` or `false` that the filter needs.","Then `map(Screening::getFilm)` turns each screening into its film, and `distinct()`, `sorted()` and `collect(Collectors.toList())` finish the job."],"tests":[{"name":"Test 1","stdin":"","call":"List<Screening> screenings = new ArrayList<>();\nscreenings.add(new Screening(\"Vertigo\", 120, 120));\nscreenings.add(new Screening(\"Heat\", 64, 90));\nscreenings.add(new Screening(\"Alien\", 90, 90));\nscreenings.add(new Screening(\"Vertigo\", 80, 80));\nSystem.out.println(soldOutFilms(screenings));\n","expect":"[Alien, Vertigo]","hidden":false},{"name":"Test 2","stdin":"","call":"List<Screening> screenings = new ArrayList<>();\nscreenings.add(new Screening(\"Up\", 10, 90));\nSystem.out.println(soldOutFilms(screenings));\nscreenings.add(new Screening(\"Jaws\", 50, 50));\nscreenings.add(new Screening(\"Brazil\", 40, 40));\nscreenings.add(new Screening(\"Up\", 90, 90));\nSystem.out.println(soldOutFilms(screenings));\n","expect":"[]\n[Brazil, Jaws, Up]","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+List<String>\\s+soldOutFilms\\s*\\(\\s*List<Screening>\\s+\\w+\\s*\\)","message":"Write the method as `public static List<String> soldOutFilms(List<Screening> screenings)`."},{"pattern":"\\.filter\\s*\\(\\s*Screening\\s*::\\s*isSoldOut\\s*\\)","message":"Keep the sold-out screenings with `filter(Screening::isSoldOut)`."}],"forbid":[{"pattern":"\\b(for|while)\\b","message":"Use a stream this time, not a loop."}]}]}