mars 18, 2016 Johan Sjöblom

In his talk at JFokus titled ”Java Streams: Beyond the Basics”, Simon Ritter introduces us to streams which were added in JDK 8. It was a good introductory talk, aimed at those with little experience in the functional programming paradigm.

To start with, let’s take an example of old pre-Java 8 style code as well as new using streams. Both will render the same result.

int getHighestSalary(List<Employee> employees) {
  int highestSalarySoFar = 0;
  for (Employee employee : employees)
    if (employee.getAge() < 30)
      if (employee.getSalary() > highestSalarySoFar)
        highestSalarySoFar = employee.getSalary();

  return highestSalarySoFar;

In Java 8 we can now write the code like this, which is arguably easier to read and can be run in parallel:

int getHighestSalary(List<Employee> employees) {
      .filter(employee -> employee.getAge() < 30)

A stream is basically a pipeline of operations. It consists of a source (usually the stream() method) which is passed to zero or more intermediate operations, and then finally to a terminal operation. The intermediate operations are a chain of operations which takes as input a stream, does something and generates as output a stream. An example is the filter() and map() methods. The terminal operation produces a result or a side effect. It takes a stream as input, but in contrast to the intermediate ones, it does not produce a stream as output. It might produce something else such as an int, list, or nothing, and it may have a side effect such as performing printing. Examples of terminal operations are sum(), collect() and forEach().

As Simon points out, it appears in the code as if the streams are passed from intermediate operation to intermediate operation, but for efficiency they are actually merged into a single set of operations underneath.

Delayed execution
The talk was not all about streams, but also touched the concept of lambda expressions, which were also introduced in JDK 8. Simon for instance talks about delayed execution and how it can be utilized to increase performance in certain situations. For example, consider the following log line: logger.debug(getStatusData());. This code will still call the possibly heavy getStatusData() even if the logging level is set to info. Thus, we perform the calculation despite nothing being logged. In JDK 8, however, the logging methods now have a version that takes a Supplier: logger.debug(() -> getStatusData());. If the logger doesn’t need the value, it won’t invoke the lambda. In other words, a lambda used like this is a description of how to get the message, rather than the message itself. This delayed execution can of course be used for other conditional activities as well.

Solving problems using streams
As the target audience were those that have begun to touch upon the topic of streams but have not yet gained substantial experience, Simon’s focus was to solve some problems in an iterative manner and present the drawbacks with the solutions arrived upon. Since streams and lambdas introduce the functional programming paradigm into Java, Simon tried to introduce that way of thinking with concrete examples.

One example that Simon walked us through in the presentation, was to find the longest line from a list. In the old Java style, we would write something like this:

String longest = "";
for (String s : listOfStrings)
  if (s.length() >= longest.length())
    longest = s;

This works, but is serial. It also contains mutable state (the longest variable), which makes us unable to execute the code in parallel.

A functional approach to the problem would be to use recursion.

String findLongestString(String longestSoFar, List<String> list, int index) {
  longestSoFar = getLongest(longestSoFar, list.get(index));

  if (index < list.size() - 1)
    longestSoFar = findLongestString(longestSoFar, list, index + 1);

  return longestSoFar;

String getLongest(String x, String y) {
  return x.length() > y.length() ? x : y;

This code has solved the issues we had with the serial version; there is no mutable state and the code can be executed in parallel. The problem here is that large input will generate OutOfMemoryExceptions, as each recursive call generates a new stack frame.

However, from these lessons, as Simon argues, we can arrive at a better solution using streams. The stream API uses the well known filter-map-reduce pattern, and in this case, we are really just interested in the reduce part; all the data is there and no transformations are needed, we just need to reduce the data set into the longest line. The reduce method has the following declaration: Optional reduce(BinaryOperator accumulator). A BinaryOperator is a subclass of BiFunction, but while BiFunction takes in two arguments of type T and U and produces a result of type R, the BinaryOperator has the same type for both the arguments and return type. It takes as input a partial result and the next element, and returns a new partial result — basically like the recursive solution, but without the stack frames.

Thus, we get

String longestLine =
    .reduce((x, y) -> x.length() > y.length() ? x : y)

Here, x will hold the partial result, thus maintaining the state, and can be seen as analogous to the longestSoFar variable in the recursive approach.

The talk ended with a sneak peak into the crystal ball to discuss some stream related features of JDK 9. Nothing breath-taking; several more classes will have support for the stream() method, for instance will return a stream consisting of either one or zero elements.

A nice addition to the stream API will be the takeWhile() and dropWhile() methods.

Stream takeWhile(Predicate p) will select elements from the stream until the predicate matches. One of course needs to be careful when dealing with unordered streams.

An example:
    .mapToInt(i -> Integer.parseInt(i))
    .takeWhile(i -> i < 71)

Analogous is dropWhile() — it will ignore all values in the stream until the predicate is satisfied.

In summary, Simon Ritter gives a nice introduction to streams and lambdas that were added in JDK 8, and provides quite a few examples aimed at getting people to think in terms of functional programming. Many of the examples are geared at providing a naive solution that yields the sought result but in a suboptimal way, then iterating over the problem until a suitable functional approach is reached. The talk was overall pedagogical and pleasant.

0 Kommentarer

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *