Tuesday, May 20, 2014

Java Streams - Action and Continue

I was talking to someone who wanted to be able to perform an action on a stream without causing a terminal operation. To me the solution seemed obvious; use a map operation which returns the input for the output after performing an action on the object.

Consider an example where we have a List<String> that we wish to map/reduce and perform an action on the reduction, but we also want a count of the entities we encounter.

List<String> strings = Arrays.<String>asList(
        "k-1", "k-2", "k-3", "x-4", "x-5", "x-6", "k-7");
long p = strings.stream()
        .filter(n -> n.startsWith("k-")) // Filter starting with 'k-'
        .map(n -> n.substring(2))        // Map to number in string.
        .filter(NumberUtils::isDigits)   // Include integers only.
        .map(n -> Integer.parseInt(n))   // Map to int
        .map(n -> {
            System.out.println(n);       // Perform action.
            return n;                    // Return input as output.
        .count();                        // Terminate stream.
                "There were %d items of %d processed.\n",
                p, strings.size());

Above in steps 1 - 4 we perform a series of map/reduce operations to retrieve the integer portion of the string "k-{n}". In steps 5-6 here we would normally use a forEach terminal operation we instead use a map intermediate operation which allows us to perform an action, returning the input so the stream stays open. In step 7 we finally invoke the terminal operation count which terminates the stream.

Note: in the above I have multiple filter/map operations that could be combined into single operations, that is perfectly fine; I recommend separating the tasks as it helps express your intention which can make it more readable.