Java was designed as a general-purpose programming language with class-based object-orientation at its core.
With the release of version 8 in 2014, a more functional programming style became viable. This story tries to shed some light on the depth of functional programming possible.
What Is Functional Programming?
The functional programming (FP) paradigm evolved from the invention of Lambda calculus by Alonzo Church in 1936, “a formal system in mathematical logic for expressing computation based on function abstraction and application using variable binding and substitution” (Wikipedia).
“par·a·digm | \ ˈper-ə-ˌdīm
a philosophical and theoretical framework of a scientific school or discipline within which theories, laws, and generalizations and the experiments performed in support of them are formulated.”
With the help of a declarative programming style, FP tries to bind our code in pure mathematical functions to build evaluable expressions, instead of statements.
Core Concepts of Functional Programming
A short overview of the pillars that functional programming is built upon, so we can later check if Java fits in, or not.
A pure function has two elemental properties:
- The same input generates the same output.
- No side-effects, e.g., affecting global state or changing argument values.
These two properties guarantee that pure functions can safely be used in any environment, even in a parallel/concurrent fashion.
Due to the predictable result for non-changing input arguments, we could replace a once-run function with its return value. This is called referential transparency.
It’s a result of pure expressions and can be used as an optimization technique called memoization, or “function call caching”, to save unnecessary re-calculations of already evaluated expressions.
Mutating data-structures can create unexpected side-effects, and we must check against them. With immutability, we can keep our data structures predictable and side-effect free, and easier to reason with.
Data-structures shouldn’t be modifiable after being initialized. To change/mutate a data-structure, we must create a new variable containing the changed value.
Just like pure functions, we can safely use any data-structure without side-effects, even in parallel/concurrent environments, without uncertain results or issues due to unsynchronized access, or changes to out-of-scope state.
A self-calling function that acts like a loop, executing the same expression multiple times until an end condition is reached and returns a “non-self-function call”.
This is important, or we easily end up with infinite recursions, until we get a
First-class and higher-order
Functions are supposed to be a first-class-citizen like any other value, so they need to be able to be assigned to variables, used as arguments by other functions, and be able to be returned by functions.
A higher-order function is defined by accepting one or more functions as arguments, or returning a function as its result, or both. This is necessary for the next pillar.
The combination of pure functions to create a more complex expression. This way, the initial functions can be small, on-topic, and reusable, but the composed function will perform a more complete task in a data structure.
“In mathematics, function composition is an operation that takes two functions f and g and produces a function h such that h(x) = g(f(x))”
Functional Programming With Java
For more on Optionals, you can check out my article Better Null-handling with Java Optionals.
The general syntax of a lambda function is:
(<argument-list>) -> <body>.
From Java’s point of view it’s represented by a functional interface, which could also be represented by an anonymous class.
Both Predicates are doing the same thing, so it looks like really nice syntactic sugar.
But the interesting question is what the compiler and the JVM do behind the scenes.
Wrapped in a simple
main method, this is the result:
// ANONYMOUS CLASS 0: new #2 // class Anonymous$1 3: dup 4: invokespecial #3 // Method Anonymous$1."<init>":()V 7: astore_1 8: return // LAMBDA 0: invokedynamic #2, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate; 5: astore_1 6: return
The anonymous version is creating a new object of the anonymous class
Anonymous$1, resulting in three opcodes.
7: astore_1 because the lambda also needs to save the reference to the local variable.
The lambda version only needs a single opcode, the newly introduced opcode
Of course, the comparison of just opcode count isn’t fair, because a lot is going on behind the
We don’t see in the Bytecode how the JVM is actually handling the lambda.
Instead of linking dynamic methods, like lambdas, at compile-time, the JVM links a dynamic call site with the actual target method just before the first execution.
A bootstrap method is used once on first-call to link it and return a method handle. It’s a little bit like when we use reflection in our code, but safer, and directly by the JVM.
There are different strategies for lambda creation (dynamic proxies, anonymous inner classes,
MethodHandle), the decision is made at runtime by the JVM, not statically at compile-time.
And the bootstrap method is only called once.
As we can see, it’s not just syntactic sugar. Lambda expressions enable the JVM to optimize our functional-style code in new ways, even allowing the reuse of lambdas at the JVM’s discretion.
Functional Core Concepts and Java
So much for the technical difference of using lambdas vs. anonymous classes, let’s see how the FP paradigm holds up.
Check. Same input results in the same output. No side-effects.
In theory, referential transparency is possible. We got pure functions, and we could cache function calls.
But there are no language constructs or helpers for doing so. The JVM might optimize calls, inlining results, etc., but it’s not as easy as in other languages like Clojure.
There are a lot of immutable and de-facto immutable classes available in JDK:
String(only de-facto, hashCode is lazily computed).
- Value type wrapper, e.g.,
- Immutable collections with Java 9.
We can make our own data structures immutable, either manually, or by using a library like Immutables. But it’s still an implementation detail, not a language feature like in other programming languages.
Check. Although the Java compiler doesn’t support tail call optimization.
First-class and higher-order
But Java was always a verbose language, so it’s not as elegant as in other languages.
java.util.function.Function provides methods for functional composition:
Is Java a Functional Language?
Not really. But it was never its intent.
Even though Java ticks a lot of boxes of the functional programming paradigm on the surface, it’s not a functional language at its core.
It still is a general-purpose programming language with class-based object-orientation, but now with support for functional constructs. Nevertheless, lambdas and a more functional-like programming style are a worthwhile addition to our tool belts.
Object-oriented vs. functional. It’s not either-or.
Instead of being able to rely on language features, most of the functional concepts must be adhered to by convention and developer discipline, and are not enforced by the compiler.
We should still adopt the core concepts and constrains provided by the functional paradigm to our codebase, where appropriate.
Always an excellent idea, not just for functional programming. Thanks to third-party libraries, we can use immutable data-structures without much additional code.
No illegal state
Design your state to be always legal. Avoid nulls, exceptions, and locking/synchronization.
Dealing with the correct type, and not relying on
instanceof, helps to identify incorrect usage at compile-time.
Any unpredictable behavior or side-effects are contrary to the functional paradigm. Using libraries that utilize runtime tomfooleries with reflection and/or proxying should be avoided, if possible.
The bottom line, we should know all the concepts and how to use them. But also know when it’s appropriate to apply them to our code.
Hopefully, Java keeps evolving into a more functional territory, providing even more advantages with reduced verbosity and more concise code.
The next part will highlight the different types of available functional interfaces and how to use them.
Comparison With Other JVM Functional Languages
Just to give you a better overview, here are some other languages running on the JVM with included functional principles.
Scala, like Java, is a general-purpose language, and not a specialized functional programming language.
But in contrast to Java, it was designed with functional programming in mind from the start, with language-level features like immutability. And with great Java interop in both directions.
Where Scala is quite easy to grasp as a Java developer, Clojure is a different kind of beast. Code is data (homoiconicity), everything is immutable, data-structures are persistent, null-handling is done right, built-in memoization, and lots and lots of parenthesis.
One downside, at least in my opinion, is the Java interop if you want to use Clojure from Java. The other way around is excellent.
Kotlin is another general-purpose language, with functional programming ingrained in it. Good Java interop, first-class language for Android. Definitely worth a try.