November 17, 2017
Just a little bit of context upfront: the project on which I did the refactoring was Spring Boot based and about two years old. It did already use some functional elements, for example the new streaming-API. More advanced features were just sparely used, so there was a lot of space for introducing more functional elements.
Repeated conditional operations
For most projects that have domain entities with a large number of fields, there might be some mapping, state checking, or similar operation that involves evaluating structurally identical conditions for each field and executing some functionality, e.g. setting a new field value. Without the usage of functional elements, such code might look like this:
|
|
If you’d find such a piece of code in a real project, you might take a short moment to actually realize that the same kind of check is done over and over again. Even without the use of functional elements there might be some refactoring possible, but definitely not as much as with them. To make more explicit what’s done, we’ll first separate the condition checking from the fields that are being checked using a tiny inner class. It’ll take both Entity
instances as constructor arguments and its only method will take a method reference to a field and return the condition result as a boolean. After this refactoring, the above code fragment might look like this:
|
|
The benefits of this refactoring might not be as obvious as it gets when there are not two but twenty fields that are being checked, still the findChangedFields
method implementation is much more revealing than the first version and the added inner class is still very small. Of course you’ll be able to add new methods to the inner class, if there are other data types that need to be checked as well.
But wait, there’s more: now that the refactoring is done, you’re still left with a number of repeated if statements and if you think about it, there is actually an implicit mapping being done: from a changed field value to a string containing the changed fields name.
Implicit mappings
I call it implicit mapping, when some kind of mapping done, but there is no actual map of what to map and structure and data are mixed within the code. Real functional languages like Haskell are really good in separating structure and data, so let’s try if Java can do that as well. First, we’ll need the actual map to operate on. Because we don’t want to instantiate this mapping every time we use it, we’ll create it as a static final
class field. This is the single part that isn’t as nice as I’d like it to be, because we’ll need a static
-Block to fill this map. Help might be found in libraries like Vavr, that bring new Maps with them that allow initialization inside the constructor or chained builders. When we have this mapping, all that is left to do is iterating the map, evaluating the condition for every entry, do the mapping for all matched elements and add each result to our list. Of course, this can be done with a lovely little Stream
:
|
|
Now we have our explicit mapping that states which fields should be mapped to which name and clearly separated from it, in the original findChangedFields
method, we iterate this mapping, filter for all fields that are changed, map to their name and collect a list of all those names of changed fields. At this state we might simply stop and settle with the results, especially if there are fields of multiple data types being checked.
Single method classes
But in this example, we’re left with a class containing just a constructor and a single method. As Jack Diederich described in his talk Stop writing classes, those might be replaced by plain functions. Because our constructor takes two arguments, we have to use a lambda expression to achieve this goal.
|
|
What’s now leftover is pure functional joy. The signature of our new method might be a bit complex at first, but in the end it’s just a predicate that evaluated a given function for the two given Entity
arguments. A Predicate
differs from a Function
in that it always returns an unboxed boolean
, so it can be evaluated as a conditional without doing an additional null check for the returned value. Although I prefer this approach to the inner class, they’re both perfectly fine, especially if - speaking for this example - there are multiple data types being checked, as each type would need it’s own lambda returning method. Also developers not so used to functional programming might stumble at that point, because it’s easy to confuse the when and where which parameter of the changedFields
method is passed.
Overuse of functional interfaces
While there are a lot of good usages for lambdas, it’s also possible to misuse or overuse them. A type of over usage that I’ve seen are utility classes containing no actual methods but variables of type Function
, Supplier
, BiFunction
, Consumer
, or any other kind of functional interface. This might seem reasonable when those functions are only or primarily used as arguments to stream methods or other functional APIs, but otherwise there is no real benefit by doing so. If you want to use those functions directly, you’ll always have to append an additional .apply
, .consume
, or such. That alone might not be too bad, but the declaration of those functions is also and even more unnecessarily long and cluttered. To show you what I mean, let’s take an example mapping function that gets the credentials for an user object:
|
|
I have to admit that this is a terrible example to some degree, because the whole function could easily be replaced by a single method reference instead. Still my point holds, that the conventional java method is better readable and it does not introduce any restrictions compared to the Function
-variable. Also the usage of both (at least for me) looks better for the conventional method.
Conclusion
The functional elements of Java 8 are nice to refactor existing programs and reduce their complexity. Nonetheless it’s not always a good idea trying to use those elements everywhere possible. So from my view, the answer to the question “Should I do more functional in Java?” is a clear “it depends”.