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
andfilter
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 outnull
values.filter(Dog.class::isInstance)
only items that are Dog instances get through.map(Dog.class::cast)
cast themammal
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 Stephan H Wissel on 06 November 2020 | Comments (3) | categories: Java