{"id":"streams-terminal","slug":"terminal-operations","title":"Ending a stream: count, sum, average, collect","text":"A stream chain has two kinds of steps. The steps in the middle, such as `filter`, are **intermediate operations**: each one gives a new stream, so another step can follow it. The last step is a **terminal operation**: it ends the stream with a result, such as the count, or with an action, such as `forEach`'s printing. After a terminal operation, the stream is used up.\n\nThis step goes through the terminal operations you'll use most. The data is a household budget: a month's expenses, in whole dollars.\n\n### count\n\n`count()` returns how many values reach it, as a `long`. A `long` doesn't fit into an `int` variable, even when the number is small:\n\n```java\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Integer> expenses = new ArrayList<>();\n        expenses.add(54);\n        expenses.add(120);\n        int count = expenses.stream().filter(amount -> amount > 100).count();\n        System.out.println(count);\n    }\n}\n```\n\n```javac\nMain.java:8: error: incompatible types: possible lossy conversion from long to int\n        int count = expenses.stream().filter(amount -> amount > 100).count();\n                                                                          ^\n1 error\n```\n\nMake the variable a `long`, as in step 1.\n\n### sum, with mapToInt\n\nA stream made from an `ArrayList<Integer>` holds Integer objects, and such a stream has no `sum` method. `mapToInt` turns it into an **IntStream**, a stream of plain `int` values, which has `sum`:\n\n```java\nArrayList<Integer> expenses = new ArrayList<>();\nexpenses.add(54);\nexpenses.add(120);\nexpenses.add(18);\nexpenses.add(35);\nexpenses.add(9);\nexpenses.add(64);\nint total = expenses.stream()\n        .mapToInt(amount -> amount)\n        .sum();\nint small = expenses.stream()\n        .filter(amount -> amount < 40)\n        .mapToInt(amount -> amount)\n        .sum();\nSystem.out.println(\"Total: \" + total);\nSystem.out.println(\"Under 40: \" + small);\n```\n\n```output\nTotal: 300\nUnder 40: 62\n```\n\n- `mapToInt(amount -> amount)` gives each Integer back as an `int`: the lambda returns its value as it is, and Java turns the Integer into an `int` by itself, as module 13 described. `mapToInt(Integer::intValue)` says the same with a method reference.\n- `sum()` returns an `int`: 54 + 120 + 18 + 35 + 9 + 64 = 300.\n- `filter` can come before `mapToInt`, and then only the values that got through are added: 18 + 35 + 9 = 62.\n\n### average, with getAsDouble\n\nAn IntStream also has `average()`: the sum divided by the count, as a double. There's a catch: an empty stream has no average, so `average()` doesn't return a plain double. It returns an **OptionalDouble**, a box that may hold a double or be empty. `getAsDouble()` takes the double out:\n\n```java\nArrayList<Integer> expenses = new ArrayList<>();\nexpenses.add(54);\nexpenses.add(120);\nexpenses.add(18);\nexpenses.add(35);\ndouble average = expenses.stream()\n        .mapToInt(amount -> amount)\n        .average()\n        .getAsDouble();\nSystem.out.println(\"Average: \" + average);\n```\n\n```output\nAverage: 56.75\n```\n\n227 / 4 is 56.75. When the box is empty, `getAsDouble()` crashes the program, here because no expense is over 500:\n\n```java\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Integer> expenses = new ArrayList<>();\n        expenses.add(54);\n        expenses.add(120);\n        double average = expenses.stream()\n                .filter(amount -> amount > 500)\n                .mapToInt(amount -> amount)\n                .average()\n                .getAsDouble();\n        System.out.println(average);\n    }\n}\n```\n\n```crash\nException in thread \"main\" java.util.NoSuchElementException: No value present\n\tat Main.main(Main.java:12)\n```\n\nSo before you ask for an average, make sure there's at least one value: check that the list isn't empty, or that the filter's `count()` is above 0.\n\n### collect\n\n`collect(Collectors.toList())` gathers the values that reach it into a new list. `Collectors` lives in `java.util.stream`, so it needs an import, and the new list's type is `List` (module 35):\n\n```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        ArrayList<Integer> expenses = new ArrayList<>();\n        expenses.add(54);\n        expenses.add(120);\n        expenses.add(18);\n        expenses.add(35);\n        expenses.add(9);\n        expenses.add(64);\n\n        List<Integer> big = expenses.stream()\n                .filter(amount -> amount >= 50)\n                .collect(Collectors.toList());\n        System.out.println(big);\n        System.out.println(big.size() + \" of \" + expenses.size());\n    }\n}\n```\n\n```output\n[54, 120, 64]\n3 of 6\n```\n\nThe new list keeps the stream's order, and the old list doesn't change. Java 16 added a shorter ending, `.toList()`, whose list can't be changed afterwards; this course uses `collect(Collectors.toList())`, which you'll see in a lot of Java code.\n\n### forEach\n\nYou met `forEach` in step 2. It's a terminal operation too: it runs its lambda for each value, and returns nothing.\n\n| Terminal operation | Gives |\n| --- | --- |\n| `count()` | how many values, as a `long` |\n| `sum()` (after `mapToInt`) | their sum, as an `int` |\n| `average()` (after `mapToInt`) | an OptionalDouble; `getAsDouble()` takes out the average |\n| `collect(Collectors.toList())` | a new `List` of the values |\n| `forEach(...)` | nothing: runs the lambda for each value |\n","task":"Fill in the blanks so that the program prints the month's total and the average expense. The average is the stream's own `average()`, which divides the sum by the count. The program prints:\n\n```\nTotal: 240\nAverage: 48.0\n```\n","kind":"fill","seed":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Integer> expenses = new ArrayList<>();\n        expenses.add(72);\n        expenses.add(15);\n        expenses.add(98);\n        expenses.add(31);\n        expenses.add(24);\n\n        int total = expenses.stream()\n                .[[mapToInt]](amount -> amount)\n                .sum();\n        double average = expenses.stream()\n                .[[mapToInt]](amount -> amount)\n                .average()\n                .[[getAsDouble]]();\n        System.out.println(\"Total: \" + total);\n        System.out.println(\"Average: \" + average);\n    }\n}\n","solution":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Integer> expenses = new ArrayList<>();\n        expenses.add(72);\n        expenses.add(15);\n        expenses.add(98);\n        expenses.add(31);\n        expenses.add(24);\n\n        int total = expenses.stream()\n                .mapToInt(amount -> amount)\n                .sum();\n        double average = expenses.stream()\n                .mapToInt(amount -> amount)\n                .average()\n                .getAsDouble();\n        System.out.println(\"Total: \" + total);\n        System.out.println(\"Average: \" + average);\n    }\n}\n","hints":["A stream of Integers has no `sum` or `average`: it needs a step that turns it into a stream of `int` values first. And `average()` gives a box, not the double itself.","The step that turns the Integers into `int` values is `mapToInt`. `average()` returns an OptionalDouble, and the method that takes the double out of it is `getAsDouble()`."],"tests":[{"name":"Output","stdin":"","expect":"Total: 240\nAverage: 48.0","hidden":false}],"require":[],"forbid":[],"more":[{"task":"What does this program print?\n","kind":"predict","seed":"import java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Integer> expenses = new ArrayList<>();\n        expenses.add(40);\n        expenses.add(8);\n        expenses.add(150);\n        expenses.add(22);\n\n        List<Integer> cheap = expenses.stream()\n                .filter(amount -> amount < 50)\n                .collect(Collectors.toList());\n        int cheapTotal = cheap.stream()\n                .mapToInt(amount -> amount)\n                .sum();\n        long pricey = expenses.stream()\n                .filter(amount -> amount >= 50)\n                .count();\n        System.out.println(cheap + \" \" + cheapTotal);\n        System.out.println(pricey + \" \" + expenses.size());\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Integer> expenses = new ArrayList<>();\n        expenses.add(40);\n        expenses.add(8);\n        expenses.add(150);\n        expenses.add(22);\n\n        List<Integer> cheap = expenses.stream()\n                .filter(amount -> amount < 50)\n                .collect(Collectors.toList());\n        int cheapTotal = cheap.stream()\n                .mapToInt(amount -> amount)\n                .sum();\n        long pricey = expenses.stream()\n                .filter(amount -> amount >= 50)\n                .count();\n        System.out.println(cheap + \" \" + cheapTotal);\n        System.out.println(pricey + \" \" + expenses.size());\n    }\n}\n","hints":["`collect` makes a new list of the expenses under 50, in the old list's order. A list can make a stream of its own, so `cheap.stream()` works like any other.","The cheap ones are 40, 8 and 22, which add up to 70. Only 150 is 50 or more, and `expenses` still has all 4."],"tests":[{"name":"Output","stdin":"","expect":"[40, 8, 22] 70\n1 4","hidden":false}],"require":[],"forbid":[],"lines":["[40, 8, 22] 70","1 4"]},{"task":"Write the method `bigExpenses(List<Integer> expenses, int limit)`, which returns a new list of the expenses of at least `limit` dollars, in the same order. Use a stream that ends with `collect(Collectors.toList())`. The import for Collectors is there already. The check runs:\n\n```java\nList<Integer> expenses = new ArrayList<>();\nexpenses.add(54);\nexpenses.add(120);\nexpenses.add(18);\nexpenses.add(64);\nSystem.out.println(bigExpenses(expenses, 60));\n```\n\nand expects:\n\n```\n[120, 64]\n```\n","kind":"code","seed":"import 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<Integer> expenses = new ArrayList<>();\n        expenses.add(54);\n        expenses.add(120);\n        expenses.add(18);\n        expenses.add(64);\n        System.out.println(bigExpenses(expenses, 60));\n    }\n\n    // Write the method bigExpenses here.\n}\n","solution":"import 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<Integer> expenses = new ArrayList<>();\n        expenses.add(54);\n        expenses.add(120);\n        expenses.add(18);\n        expenses.add(64);\n        System.out.println(bigExpenses(expenses, 60));\n    }\n\n    public static List<Integer> bigExpenses(List<Integer> expenses, int limit) {\n        return expenses.stream()\n                .filter(amount -> amount >= limit)\n                .collect(Collectors.toList());\n    }\n}\n","hints":["The header is `public static List<Integer> bigExpenses(List<Integer> expenses, int limit)`. The method can return the stream's result straight away.","`return expenses.stream().filter(amount -> amount >= limit).collect(Collectors.toList());`"],"tests":[{"name":"Test 1","stdin":"","call":"List<Integer> expenses = new ArrayList<>();\nexpenses.add(54);\nexpenses.add(120);\nexpenses.add(18);\nexpenses.add(64);\nSystem.out.println(bigExpenses(expenses, 60));\n","expect":"[120, 64]","hidden":false},{"name":"Test 2","stdin":"","call":"List<Integer> expenses = new ArrayList<>();\nexpenses.add(30);\nexpenses.add(29);\nexpenses.add(31);\nexpenses.add(30);\nList<Integer> big = bigExpenses(expenses, 30);\nSystem.out.println(big + \" \" + expenses);\nSystem.out.println(bigExpenses(expenses, 100) + \" \" + bigExpenses(expenses, 0).size());\n","expect":"[30, 31, 30] [30, 29, 31, 30]\n[] 4","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+List<Integer>\\s+bigExpenses\\s*\\(\\s*List<Integer>\\s+\\w+\\s*,\\s*int\\s+\\w+\\s*\\)","message":"Write the method as `public static List<Integer> bigExpenses(List<Integer> expenses, int limit)`."},{"pattern":"\\.collect\\s*\\(\\s*Collectors\\s*\\.\\s*toList\\s*\\(\\s*\\)\\s*\\)","message":"End the stream with `collect(Collectors.toList())`."}],"forbid":[{"pattern":"\\b(for|while)\\b","message":"Use a stream this time, not a loop."}]}]}