Functional Enlightenment and the Java Streams API
Functional Programming with Java
Early in my career, I had a lead developer on a project who during code review, much to my chagrin, kept making me re-write what was in my opinion “perfectly good, working code”. To my lead however, the code was utter crap. Frequently, the piece of code in need of refactoring involved situations where a collection of items would be iterated over, with either some action performed on each item, or some piece of information extracted and collected.
These situations are perfect for a feature that was added to Java starting in version 8: The streams API. The streams library introduces constructs from Functional programming – something that readers familiar with Haskell, Lisp, and more and more frequently javascript should be comfortably familiar with.
Streams
"A sequence of elements supporting sequential and parallel aggregate operations." - Oracle Streams API Documentation
So what are streams, anyway? According to the oracle java documentations streams are “A sequence of elements supporting sequential and parallel aggregate operations.” Most frequently, an array or list - a collection - serves as the input to a stream.
Another important thing to remember about the streams api is that it embraces another principle from functional programming: immutable data. The operations performed on the input list, generates a new output list, leaving the original list unchanged. This is nice because it allows us to perform many, many operations on a single list without the need to first make a copy.
Working on Streams
Now that we know what a stream is, what can we do with it? Lots of stuff! The most common
operations on stream are
- filter() – return a stream that has extracted or weeded out values that meet certain criteria from the input list.
- map() – map returns a stream of the results of performing a function on each member of the stream, such as converting between types, or performing some type of computation.
- distinct() – returns a stream of the items with no duplicates
- sorted() – arranges the stream according to some ordering. limit() – returns a stream of the first x items.
For a complete list of the Java Streams API, take a look at the Oracle Java documentation. Because each method takes a stream() as input and outputs a stream(), these methods can be “chained” together to create very powerful constructs with very little code - and it is this that my previously mentioned code reviewer was trying to bring home. Part of what makes streams so powerful is how expressive they are, maintaining code readability while offering tremendous ability.
Gathering the output from Streams
Now that we’ve processed our data, we need to gather the result. Remember: the input list to
the stream api remains unchanged. It’s also important to remember that what a stream operation
returns is a stream. In order to gather the results, we need to enlist the help of Collectors to convert the streams back to a list at the end, as well as some methods that java provides to make our life a bit easier:
- toList() – returns the streams values as a List<T>
- toArray() – returns the values of the stream as a T[]
- Collet() – takes a specified “Collector” and returns the items as a Set, List, String, or whatever Collector you specify.
Having started from our input list, and ended with our output list, while keeping our input unchanged while avoiding all kinds of named objects, control flow, and all the other excess baggage that comes with using the more traditional iterative approach – lesson 2 that my code reviewer was instilling in me. What’s more is that java borrows another concept from functional programming with the streams API: Lazy evaluation. This means that until the results are needed – i.e. we specify a collector and save the result, no computations are performed.
An Example
For this example I've created a base class Animal, from which Dog and Cat derive. I then implemented two functions:
- getDogNames() - which returns the names of the dogs in the collection using the traditional imperative style.
- getCatNames() - which returns the names of the cats in the collection, using the Functional Programming style of the Streams API
Both functions do what is intended, but getCatNames()'s functional style is shorter, easier to read, and gets on with less named object creation.
Wrapping up, but just beginning
Once you really grok streams and, can wield their power according to your will, I truly hope you
will have the kind of experience I did: the moment of enlightenment when you look at an 11 line
method, and refactor it down to 3 lines that are both more performant and easier to read than the code you started with - and if you were to look up at that moment and notice that the stars above have transformed into the shape of a lambda symbol….
Ok so maybe that last part is a bit of a stretch, but the java Streams api offers a fantastic way to dip your toes into the world of functional programming, and is an excellent skill to have.
-
Generating P-Code by AST Traversal
-
Ternary Search Tries: String Specific Ordered Symbol Tables
-
Digital Search Trees
-
Lossless Compression Part III: Huffman Coding
-
Lossless Compression Part II: The LZ77 Algorithm
-
Lossless Compression Part I: Working with Bits in a Byte Oriented World
-
Bottom Up AVL Tree: The OG Self-Balancing Binary Search Tree
-
A Different Take on Merge Sort: Binary Search Trees?
-
Deleting Entries From Open-Address Hash tables
-
Transform any Binary Search Tree in to a Sorted Linked List
Leave A Comment