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 (0) | categories: Java

Comments

  1. No comments yet, be the first to comment