wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Streams and Functional programming in Java


I'm late to the party embracing Streams and functional interfaces in Java. Using them for a while taught me the beauty and how things fit together nicely

Moving parts

  • At the beginning a class implementing the Stream interface emits items, that can be manipulated using map and filter operationf
  • The map and filter operations are supported by the Interfaces in java.util.function (we get to the samples later)
  • At the end the result gets "collected", in its simplest form using .forEach or, more sophisticated using a Collector with many ready baked options

What's the big deal?

short answer: clean, terse and clutter free code.

long answer: an example. Lets say you have a mammal class which gets subclassed by cat and dog (and others). You have a collection of these mamals and need to extract all dogs over weight 50. Weight is not a property of mammal. There might be null values in your collection. Classic code would look like this:

List<Dog> getHeavyDogs(final List<Mammal> mammals) {
    List<Dog> result = new ArrayList<>();
    for (int i = 0; i < mammals.size(); i++) {
      Mammal mammal = mammals.get(i);
      if (mammal != null) {
        if (mammal instanceof Dog && ((Dog) mammal).weight() > 50) {
          result.add((Dog) mammal);
        }
      }
    }
    return result;
  }

We all seen this type of code. In a functional and stream style this would look different. We have a little duck typing going on here. When a method looks like a functional interface, it can be used as this function. E.g. a method that takes one value and returns a boolean can be used as a Predicate, which comes in handy for filter operations. Another nifty syntax: you can address methods, both static and instance using the :: (double colon) syntax. So when you could use a lambda x -> this.doSomething(x) you can simply write this::doSomething and the compiler will sort it out (System.out::println anyone?)

Rewriting our example turns out like this:

List<Dog> getHeavyDogs(final List<Mammal> mammals) {
    return mammals.stream()
        .filter(Objects::nonNull)
        .filter(Dog.class::isInstance)
        .map(Dog.class::cast)
        .filter(dog -> dog.weight() > 50)
        .collect(Collectors.toList());
  }

Much cleaner, and it's Java without curly brackets. You could even skip the line with Objects::nonNull since class::isInstance always returns false for null values. I kept it here to introduce Objects, a neat helper class.

Lets break it down:

  • mammals.stream() provides a stream of mammal instances
  • .filter(Objects::nonNull) uses a static method to clear out null values
  • .filter(Dog.class::isInstance) only items that are Dog instances get through
  • .map(Dog.class::cast) cast the mammal into a dog. You are not limited by casts, any function can be used
  • .filter(dog -> dog.weight() > 50) filter out the heavy dogs
  • .collect(Collectors.toList()); put the result into a list

The DominoJNX API makes heavy use of streams, but that's another story for another time. There's more to functional interfaces, streams and collectors (sums, groups, computations etc), so never stop learning!

As usual YMMV


Posted by on 06 November 2020 | Comments (3) | categories: Java

Comments

  1. posted by Frank van der Linden on Saturday 07 November 2020 AD:

    Streams are very powerfull and makes it easier to read, but..... they are performance wise not the fastest solution. At Spring One 2019 I attended a session about performance and there were stats that in lots of cases the for loop was way faster than the streams API


  2. posted by Stephan Wissel on Monday 14 December 2020 AD:

    Frank, you are right. Streams require some overhead that often makes them slower that classic for loops. AFAIK streams start to outperform the loop once you run them in parallel, an option not available to a for loop.

    I our case, the number of elements in each stream is small, so the speed differences tend to be within the margin of error of measurement.

    Cleaner code clearly (pun intended) was worth the trade off


  3. posted by Novel Best on Wednesday 15 September 2021 AD:

    Stream is a very good function, we use lot after Java 8