Clever enough to write it, wise enough to delete it
Published: 2021-09-07 14:06:27

Or “How I learnt about the hacky world of Java Functional Interfaces”

by Pete McCarthy – November 1, 2019

Java 8 can be seen as one of the major milestones of the language in terms of what it added and how it changed how Java code can be written. Akin to Generics from 2004’s JDK 5 release, Lambdas and Functional features were the significant new addition to Java SE 8. With much of the industry now warming to the concepts of functional programming, no longer an exclusive resident of the academic world, new methodologies like futures/promises have been shown to work on the the JVM in languages such as Scala.  These ideas have fundamentally changed how modern codebases can be built to make use of the unique and concise properties that the functional paradigm has to offer.

So what happens when you combine the world’s most popular programming language with this radically new methodology for writing code? It can feel  similar to mashing your favourite action figures together while Oracle’s narration booms “now kiss”.

Java has preserved backwards compatibility while it introduced functional programming elements, and in places this can feel quite painful. While Java’s incredibly flexible interface system is typically a staple of any codebase, using it for  functional programming can feel like the abstraction bucket is leaking so badly that it barely holds any water at all.

This article tells the story of a problematic piece of code, a clever solution, followed by the (good!) decision to throw out the clever solution and replace it with a stupid one.
The Initial Problem

The problem begins with reflection; in one of our server modules, we had a system which mapped multiple alternative names to a class:

Map myMap<String, Class<? Extends Converter>

This meant we could use meaningful class names, but still refer to them by helpful names in config (e.g. “Default”) in our config system. The problem came with what was done with the returned class.

With the class retrieved, a new object would attempt to be created:

Converter myConverter =
    configMap.getConverter(name).getConstructor(...).newInstance(...);

This tried to find a matching constructor, call it and return an object. This works fine in a perfect hierarchy, where you know beforehand that all Converters inherit a common superclass. This also resulted in neat, general and concise code – a few lines seemingly accounted for every possible converter.  However, if you aren’t armed with this hierarchical knowledge beforehand, and your class does not have the expected constructor, the compiler wouldn’t complain, but the program would fail horrendously at runtime

The problem was a consequence of this; some changes added a new converter, but this new one was so novel that it had no need to inherit from the base superclass (it was a dumb implementation and simply did nothing). When the new deployment was spun up, we were greeted with a nasty gotcha stating the constructor couldn’t be found. This spearheaded the motivation to remove reflection entirely and rewrite it in a more concrete and understandable way.
The Functional (Clever) Solution

The aim of this solution was to meet two goals:

    Make the compiler whinge at you if you don’t meet the converter’s constructor signature
    Make the code as concise as it was when using reflection

 

What were we doing before? We were simply calling a constructor on some object dynamically identified at runtime. Could this be achieved at compile time instead? Could we call one method to construct a new object which still makes use of dynamic dispatch?

Yes, by making use of method references.

Java 8’s warehouse of functional features added a new operator: the double colon (::). This is used to retrieve a method reference. That is, essentially, a pointer to the method that is already bound to an instance of its class. For example, to get a reference to println, you could use this:

System.out::println

This is an expression – i.e. it can be assigned it to a variable, which can be called similarly to a lambda function that has been assigned to a variable. The way Java 8 does this is by using a functional interface. A functional interface is an interface with only one method. If a method takes a functional interface as an argument, you can pass a method reference (or a lambda function) instead of an instance of that interface.

So in the above case, the functional interface for System.out::println would look like:

public interface PrintlnFuncInterface {
    void doit();
}

And this means we can assign the method reference into a variable of that type:

PrintlnFuncInterface printlnRef = System.out::println;

This can also be used for constructors, which looks as simple as:

FooConstructorInterface fooConstructor = Foo::new;

Our solution to the initial problem used these features in a new implementation for the config map. Instead of holding classes in its values, the new map now looked like this:

Map<String, ConverterConstructor>

where ConverterConstructor is a functional interface matching the signature of the constructors for Converter implementations. The whole process of creating a new converter was now simply:

    Retrieve the constructor from the map as a functional interface
    Call this functional interface’s method to construct a new object dynamically

 

This seemingly met both goals. Super-concise code, but the compiler would moan if you tried to add a constructor which didn’t match the functional interface’s signature. Win-win.
Limitations of the functional approach

While this seemed to work perfectly in practice, there were a number of issues which ultimately coerced me into rewriting this “clever” code into stupid code using simple if-else statements. In summary, these were:

    Too much setup code
    Not general enough
    Cohesiveness

 

To get this whole system to work required a large amount of changes to both the core logic of the original mapper and additional classes that seem to just add bulk. On that latter part, around five empty interfaces simply needed to exist to satisfy the functional constraints. This was because a functional interface needed to be created for each converter with just one method which matched the respective converter’s constructor.

It padded out much of the code and added unneeded complexity, making it harder to know at a glance what was going on unless the reader was adept in Java’s unique functional ways. On top of this, adding and fetching the constructors from the map now needed separate methods for each constructor, adding at least seventy lines of code to the mapper – all this so that the user could call two lines of code to get their constructor. Not a satisfactory tradeoff.

The limitations of functional interfaces also meant that some of the code wasn’t general enough, despite being one of the key driving points for its use. Essentially, one of the converters inherited a class with a constructor as such:

public Foo(int x, int y, int z){...}

But one of its subclasses had only this as a constructor:

public SubFoo(int x) {super (x, 2, 3)...}

This meant one functional interface could not satisfy the entire class hierarchy. Simply put, there was no easy way around this other than changing the core logic of the converter’s class in question, so a work-around which didn’t use functional aspects at all needed to be implemented, defeating the whole point of using it in the first place.

Lastly, the code was just too complex for what was trying to be achieved. How would the client/caller of this code expect it to work? A new starter, for example, with no knowledge of Java’s functional concepts would have to look up how it worked before being able to comfortably use it. It’s another thing – arguably unnecessary – that the coder needs to know to use the codebase, and while it isn’t the most complex system to understand, it’s something additional all the same.
Rewritten Solution and Final Thoughts

So, we decided to remove the “functional” system and simply use a switch-case statement for each converter: the mapper would return a constant for each respective converter. This meant all the setup code was removed and the only real trade-off was that it took three lines of code to construct a new converter instead of two. Most of the setup code before the functional approach could be reused as well, saving a lot of time.

This experiment with functional programming for this use case was fun and interesting to try. If anything I learnt a lot about how Java handles functional concepts and what they could be best used for. However, Java is getting long in the tooth and adding support for this new paradigm, albeit rudimentary, isn’t as graceful as it is in other languages that were designed with functions as first-class concepts. It seems clear to me that classic Design Patterns are more effective and mature than functional approaches in Java. A factory for something like this seems like a perfect fit, but I hope the day will come where the microscopic niche of constructor references will prove the most effective method of implementation, whatever it may be.

It was fun to figure out how some of Java’s newer features which allowed me to solve my problem in a unique and interesting way, but it was a great learning experience to to throw all that clever code away and just solve the problem in a direct way that anyone could understand, and will definitely work 🙂