Functional Programming Patterns With Java 8
Learn more about functional programming patterns in this tutorial on writing simpler, cleaner code.
Join the DZone community and get the full member experience.Join For Free
It’s been four years since functional programming became feasible in Java. The means we have had four years to play with Java 8.
And we've played... and played. After developing several large enterprise projects that made heavy use of Lambdas and Streams, consulted many other projects in doing so, and taught these concepts to hundreds of developers as an independent trainer, I think it’s time to summarize patterns, best practices, and anti-patterns.
I wrote this article due to the level of enthusiasm received at the talks I gave this year at Devoxx. If you are interested in learning more from a passionate, entertaining, and lightning-fast, live-coding video, check out this recording of my talk.
This article will walk you through a series of simplified refactoring exercises from traditional imperative-style code to functional-style code in Java 8, continuously aiming for Simplicity Clean Code. To gain the maximum benefits from this article, you should have some practical experience with Java 8 features. I submitted each phase of the exercises on this GitHub repository, so feel free to walk through the repository yourself to see it all.
1) Prefer Named Functions Over Anonymous Lambdas
To warm up, let’s start with the simple task of bringing the details of some users to the UI. We’ll start with a traditional review of the entities list to convert each
User to a
However, I’m not very proud of this code, because it’s likely that I’ll be writing similar code repeatedly for many use cases. So, let’s cut the boilerplate using Java 8:
That’s better. But, I’m still not happy with it. This lambda I wrote demonstrates an "anonymous function." As a clean code maniac, I have a problem with that – I want expressive names. So, I quickly extracted the lambda content into a separate method:
Nice. The code was simple enough in the previous version, but now it’s slightly better. It only took me three seconds with my IDE. I always advise my trainees to master those refactoring shortcuts!
Sometimes, this conversion of logic is so trivial, as seen in this example, that we could put it directly in the DTO constructor. Please note that I would require DTO's to depend on entities, but not vice-versa (the Dependency Inversion Principle):
Now, let’s imagine that this conversion requires the help of some other component, which we would like to inject using Spring, Guice, CDI, etc. But, injecting a dependency in a class we instantiate would require very complex code. Instead, I would come back to the previous version, but if this conversion does grow too complex, we should move it to a separate
UserMapper class and reference it from there:
The key point is: always extract complex lambdas into functions with an expressive name that you can then reference using four-dots (
:: ) from:
- the same class (
- another class (
- some static helper method (
- the Stream item type (
- even some constructor (
UserDto::new), if it’s simple enough;
In short: never type
2) Stream Wrecks
Suppose you’ve worked with lambdas and streams ever since they were added to the language. And, you need to prove that, right? So, you implement a use case:
You count how many times products were ordered during the previous year. Now, take only the frequent ordered Products (>=10) and return them, if they were not logically deleted or explicitly hidden from database.
And you go home happy...
But we will find you! Management won’t be able to fire you — I agree – but, who can read that stuff?! Admit it... who will ever want to work with you?
The worst thing about this code is that each line returns a different type. You won’t see those types unless you hover the methods with your mouse in your IDE (detective work).
One of the most important rules of clean code is: small methods. So, let's break this long chain into two methods by looking at the code we see
.collect(..) immediately followed by
.stream(). Since you gathered the items in a collection anyway, why don't we explain what that collection is by extracting a method with a nice name? Furthermore, let’s replace
order.isNotDeleted(), to be able to use
But, only then do we notice that at line #6 we might be querying an external system in a loop!! OMG! And that’s something you should never-ever do. So, let’s get that
hiddenProductIds list before we start streaming. We may even go further and extract the check whether the product is hidden in a
Predicate local variable. It could help the reader, if he’s comfortable keeping a function in a variable :).
There is one more thing we could do: we could name the stream of frequent products and make it a variable of type
Stream. As we know, the
Stream items aren’t actually evaluated at this point, but only at the end, when we
.collect() it. However, working with
Stream<> variables is sometimes discouraged, because careless developers might try to re-use it (re-traverse it), so before doing it, make sure your team is fully aware of this common occurrence.
The idea here is to avoid excessive method chaining by introducing explanatory variables. This means extracting methods and even working with variables of a function or
Stream type, in order to make the code as clear as possible to your reader.
3) Fighting the Greatest Beast of All: Null Pointer
Yes! Null Pointer wasn’t always there! In fact, the cause of the most frequent bug of all was actually invented!
Let’s get rid of it now, shall we?
The exercise is simple: we need to return a nicely formatted line to print the applicable discount for a customer based on the fidelity points he gathered:
Let’s see what it returns for 60 and then for 10 points:
But, I suppose showing your user a “null” is not something you would like to do every day. Obviously, the problem is that we concatenate a potential
null integer. Fixing it is trivial:
Next, we return an empty string, if there is no discount. But, to do that, we’ve polluted our code with a null-check. And, to make matters worse, we have to find the problem we had by peeking into the
getDiscountPercentage() function to see when it might return a
null. But, this technique does not scale in large code bases. Instead, your API should make it clear that the function might return nothing.
Naturally, what I did was replace the null check with a call to
Optional.isPresent() at line 3. But, sometimes, the first idea that pops into our head is NOT always the best one. When you play with
Optional, you need to think of it the other way around. Whenever you try to change what’s in the magic box, apply a function to that box using
.map(), so that the contents of the box is transformed only in case there was something inside.
Not only is the code more concise, but it’s also easier to read once you get used to this style.
Phew! We only have one more test to do: a customer without a
KABOOM! There you go! We often rush in terror to see where the exception pops up from. Here, it’s in the first line of the
getDiscountPercentage() function. This is due to some boundary value (
null) for the
MemberCard parameter that we never handled. Let’s fix that right away – hide that bug as quickly as possible and pretend we never saw it:
See, we rushed again. And, again, we missed one design insight (do you see a pattern?). We quickly applied defensive programming here and guarded our code against all other invalid data. But, it’s said that the best defense is the offensive. What if, instead of guarding our code in fear, we said: “Wait a second. So, the member card can be absent for a customer? Then, the
Customer.getMemberCard() should return an
Yes, I’m touching on sacred things here. I dared to change the domain entity! But, I believe I made it more expressive because the link between a customer and a member card was actually optional (Note: the field and the setter still use the ‘raw’ type
MemberCard). Then, instead of testing for
null, we test if
We gained nothing! I'd say this code gets even uglier! But, there was this clean code rule: don’t take nullable parameters, because the first thing you need to do is check for null. In Java 8, this translates to don’t take Optional parameters.
Let’s twist our mind again and see that we should apply the
getDiscountPercentage() to the
MemberCard , only if there is a member card. So, let’s undo our changes to
getDiscountPercentage()go back to the
getDiscountLine() and start there from the
But, the output might surprise us:
It’s because d at line #4 is no longer an
Integer, but an
Optional<Integer>. You’ll see it yourself if you hover your mouse over the first
.map() and look at the return type:
Optional<Optional<Integer>>. Here, you got a number inside a box inside another box. It’s like wrapping your kid a present for Christmas in multiple nested wraps just to increase the thrill of discovery. In our case, we will use the monadic nature of
Optional and use
.flatMap() instead of
.map() to get rid of the extra wrapping. (To do on a cold windy night: read more about Monads). There is a lot more to using Optionals, but, in my experience as a trainer, the most difficult mental step is the one described here.
So, whenever null gives you problems in Java 8, don’t hesitate to jump on Optional and apply transformation functions on the, potentially empty, magic box. The clean code rule becomes: don’t take Optional params; instead, return an Optional whenever your function wants to signal to your caller that there might be NO return value in some cases.
4) The Loan Pattern/Passing a block
For the following exercise, let’s export the orders to a CSV file:
I’ll open a
Writer, stream over all the orders, convert them, and then write each to the file. The vague odor of fear at the end of this example stems from the possibility that, perhaps, no one will ever catch my exception afterward. Ideally, you should trust your team with these exceptions, so that if they are thrown on any threads, they are gracefully caught and logged.
But, it doesn’t compile!
This is because the
Writer.write() method declares it throws
IOException, even when the
Consumer expected by the
.forEach does not. You should suffer if you throw checked exceptions! But how to hide that checked exception thrown by the JDK class? We could expand line #7 into an inline lambda , and, then, within in that perform the following:
However, reading this would hurt our eyes. We should probably immediately bury it in some method, or we could let jOOL do that for us. Line #7 then becomes:
And we go home happy ...
There are many ways you could stray from the path of righteousness while doing that, including booleans, enum ExportType, and @Overriding concrete methods (I mention some of those in my talk, just for fun), but I will sketch here an application of the Template Method design pattern [GoF].
I want you to ask yourself: why did we use that dangerous word there? Why did we play with fire? What’s the excuse for that awful
extends in the code? To force me to provide the missing logic, there will be a function
f(Writer):void whenever I subclass the
But, we can do that a lot easier in Java 8! We just need to take a
Consumer<Writer> as a method parameter!
Wow, a lot of things changed here. Instead of
exportFile() function got a new
Consumer<Writer> parameter, which it calls to write the actual export content. To get the whole picture, let’s sketch the client code:
Here, I had to use
Unchecked again to make it to compile, because
writeOrders() declared it throws an exception! Alternatively, if you were a Lombok fan, you could drop a
@SneakyThrows on the
writeOrders(), and, then, simply delete the throws clause from it. You can try it yourself. I pushed all the steps (including this one) on this dedicated GitHub repository. I won’t debate if it’s a good practice, I’m just playing around. Also, please note that, to try it out, you will have to configure Lombok on your IDE.
The fundamental idea is that whenever you have some ‘variable logic’, you can consider taking it as a method parameter. In my training, I call this the Passing-a-Block pattern. The above example, however, is a slight variation, in which the function given as a parameter works with a resource that is managed by the host function. In our example,
OrderExportWriter.writeOrders receives a
Writer as a parameter to write the content to it. However,
writeOrders is not concerned with creating, closing, or handling errors related to the
FileWriter. That’s why we call this the Loan pattern. This is a function we pass in that is essentially borrowed so the Writer can do its job.
One major benefit of the Loan pattern is that it decouples nicely with the infrastructural code (
FileExporter) from the actual export format logic (
OrderExportWriter). Through a better separation by layers of abstraction, this pattern enables a Dependency Inversion, i.e. you could keep the
OrderWriter in a more interior layer. Because it decouples the code so nicely, the design becomes a lot easier to reason with and unit test. You can test
writeOrders() by passing it a
StringWriter and, afterward, see what was written to it. To test the
FileExporter, you can pass simply a dummy
Consumer that just writes “dummy”, and then verify that the infra code did its job.
And, this is all done without any extends. Extending to reuse logic is known to doing long-term damage to the design. Therefore, the elegant way we can use the Passing-a-Block pattern in Java 8 could be witnessing the funeral of the template method design pattern, because it achieves mostly the same goal without forcing you to extend anything.
There is one more variation of the Passing-a-Block pattern, the Execute Around pattern. Syntactically, the code is very similar:
However, the purpose is slightly different. Here,
stuff() was already implemented, but, afterward, we wanted to execute some arbitrary code around it (before and after). With Execute Around, we write this before/after code in some helper function and then wrap our original call within a call to this helper. If we look closely at the second line, it smells like Aspect-Oriented Programming (AOP) right? With Spring, we normally just put
@Transactional on a method to get a transaction for this method. For the TransactionInterceptor to come into play, however, you need to call the
@Transactional method on a (proxied) reference to this class provided by Spring, which is not always desirable. So, both the above two lines refer to the case in which you require some ad-hoc weaving, that is, to run your function within some arbitrary utility function and to do so before and/or after your code.
Disclaimer: The pattern names are used interchangeably in articles you can read on the internet.
The key takeaway of this section is that you should force yourself into thinking about handling bits of logic and juggling them as first-class citizens in Java 8.
It will make your code more elegant, simple, and expressive.
5) Five Ways to Implement Type-Specific Logic
The task is simple: there are three movie types, each with its own formula for computing the price based on the loaned number of days.
If we test:
The example is a distillation of the classic video store coding Kata of Uncle Bob. The problem in the code above could be the
switch: whenever you add a new value to the enum, you need to hunt down all the switches and make sure you handle the new case. But this is fragile. The
IllegalArgumentException will pop up, but only if you walk that path from tests/UI/API. In short, although anyone can read this code, it’s a bit risky.
One way to avoid the risk would be an OOP solution:
If you create a new type of movie, a new subclass actually, the code won’t compile unless you implement
computePrice(). But, it
extends again! What if you want to classify the movies by another criterion, say release year? Or how would you handle the ‘downgrade’ from a
Type.NEW_RELEASE to a
Type.REGULAR movie after several months?
Let's revert to the first form and let’s look for other ways to implement this. In my Devoxx talk, I also live-code how to implement this logic using abstract methods on enums. But, here, I’d like to directly throw in the change request: "the factor in the price formula for new release movies (in our example was 2) must be updatable via the database."
Auch! This means that I have to get this factor from some injected repository. But, since I can’t inject repos in my
Movie entity, let’s move the logic to a separate class:
switch is back with the inherent risks! Is there any way to make sure noone forgets to define the associated price formula?
And, now, for the grand finale:
And, instead of the
I am storing into each enum value a method reference to the corresponding instance method from
PriceService. Since I refer to instance methods in a static way (from
PriceService::), I will need to provide the
PriceService instance as the first parameter at the invocation time. And I give it
this. This way, I can effectively reference methods from any [Spring] bean from the static context of the definition of an enum value.
In the various Coding Dojos I held, developers also proposed
Map<Type,Function<>> , but compared to an old-school
switch, it doesn’t have any real benefit – the compilation still doesn’t break if you add a new movie type, but the code becomes more esoteric.
- Always extract complex lambdas into named functions that you reference using
::. You should NEVER write
- Avoid excessive call chaining — break them up using explanatory methods and variables, especially if the return type varies across these calls.
nullannoys you, think about using the
Optional. Twist your mind — you will have to apply functions to the magic box.
- Realize when the variable thing is a function, and you work with that explicitly, pass a function to another function.
- Loan Pattern means to have the function you give as the parameter that you use for a resource managed by the 'host' function. This leads to conceptually lighter, loosely coupled, and easy to test design.
- Sometimes, you might want to have some arbitrary code to execute around another function. If that is the case, pass that code to the function as a parameter.
- You can hook type-specific logic to your enums using method references to make sure each enum value is associated with a corresponding bit of logic.
Always aim for the simplest possible code and aggressively distill your code until it's trivial/"boring."
Opinions expressed by DZone contributors are their own.