
Java vs. Kotlin, Stateful Function Edition
State encapsulation or lamba expression with closure when implementing stateful functions? Is Java or Kotlin better suited for either one of those 2 approaches? Let’s find out…
Use case
To compare implementations in Kotlin and Java, we will build a very simple feature, create a function that can count how many times it was called. Basically we need something that will accept a higher-order function and hold a counter to be incremented each time that higher-order function is invoked.
Stateful Function with State Encapsulation in Kotlin
Here we can take advantage of the ability for a Kotlin class to implement a native function type ((T) -> R
in this case), and Kotlin’s operator overloading feature to override the invoke() operator, which allows to treat a class instance as a function:
We just store the higher-order function f
(which turns out to be our { i: Int -> i * i }
function) and delegate to it when we invoke our top level class InvokeAndCount
through the invoke
operator.
Stateful Function with State Encapsulation in Java
Let’s see now one possible way to implement the same functionalities in Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class InvokeAndCount<T, R> implements Function<T, R> { private final Function<? super T, ? extends R> f; private int count = 0; InvokeAndCount(Function<? super T, ? extends R> f) { this.f = f; } public Integer getCount() { return count; } @Override public R apply(T t) { count++; return f.apply(t); } } @Test void testStatefulFunction() { var square = new InvokeAndCount<Integer, Integer>(i -> i * i); rangeClosed(1, 5).forEach( i -> out.println("square of " + i + " = " + square.apply(5))); out.println("function was called " + square.getCount() + " times"); } |
By having our class directly implementing the java.util.function.Function
interface, we effectively mimic the Kotlin’s way to implement a native functional type. This is not demonstrated in the above example, but our instance of InvokeandCount
class can also be passed as a parameter to any other methods accepting a lambda expression i.e. a Function
type.
Lambda Expression with Closure in Kotlin
The concept here is to take a function as a parameter f
(which turns out to be our { i: Int -> i * i }
function) and return a new function that will call the first function f(i)
and increment our counter:
The code is very concise with functional types declarations e.g. (Int) -> Int
. Also, in Kotlin we can access and modify any variable declared outside the scope of a lambda expression, which makes it trivial to increment our count
variable.
Lambda Expression with Closure in Java
The equivalent Java implementation would look like this below. Interestingly, even if the type definitions are more verbose, it’s also self describing, it’s pretty clear just by looking at the types that we declare a function that takes a function as its parameter (UnaryOperator
) and returns another function (UnaryOperator
) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Test void testStatefulFunction() { int[] count = {0}; // Or any value holder e.g. AtomicInteger or custom class Function<UnaryOperator<Integer>, UnaryOperator<Integer>> invokeAndCount = f -> i -> { count[0]++; return f.apply(i); }; var square = invokeAndCount.apply(i -> i * i); rangeClosed(1, 5).forEach( i -> out.println("square of " + i + " = " + square.apply(i))); out.println("function was called " + count[0] + " times"); } |
One thing different though from the Kotlin example just above is that, in Java, any variable declared outside the scope of a lambda expression has to be effective final to be referenced by the lambda expression, which means it can’t be modified. This is why here we have to use a trick to use a reference to the holder of our counter (an array in this example) instead of the counter variable itself, as the reference to the array itself never change. This is an undesirable hack for sure, so ideally we never should do that… which might be a trigger to rethink our design…
Conclusion
Those examples highlight some differences between Java and Kotlin, for briefty purpose, thread safety and generic mechanism to store/retrieve any kind of state were skipped, but either using Java or Kotlin, in most cases I would stick with state encapsulation instead of lambda expression + closure, for 2 reasons:
- Easier to read and reason about the code
- Despite everything functional programming bring to the table (pure functions with no side effects, immutability, etc.), OOP still has its place in some specific scenarios… in the case you have to keep some internal states in your apis/components/etc., unless that state is also declared, initialized and only visible from within your own component, leaking your mutable state outside of the code you control is an invitation to the user of your code to manipulate your internal state without your consent… nothing good can come out of it.
Bottom line, the mouse trap is really in the choice of design and not in the choice of language, safe vs. unsafe code is independent of the language used!