This blog post appeared on DZone and JetBrains’s Blog
Suppose we want to create a simple thread that only prints something on the console:
What if we changed the value of answer
while the thread is executing?
In this article, I would like to answer this question, discussing the limitations of Java lambda expressions and consequences along the way.
The short answer is that Java implements closures, but there are limitations when we compare them with other languages. On the other hand, these limitations can be considered negligible.
To support this claim, I will show how closures play an essential role in a famous language as JavaScript.
Where Are Java 8 Lambda Expressions Coming From?
In the past, a compact way to implement the example above was to create an instance of a new Runnable
anonymous class, like the following:
Since Java 8, the previous example could be written using a lambda expression.
Now, we all know that Java 8 lambda expressions are not only about reducing the verbosity of your code; they also have many other new features. Furthermore, there are differences between the implementation of anonymous classes and lambda expressions.
But, the main point I would highlight here is that, considering how they interact with the enclosing scope, we can think of them just as a compact way of creating anonymous classes of interfaces, like Runnable
, Callable
, Function
, Predicate
, and so on. In fact, the interaction between a lambda expression and its enclosing scope remains quite the same (i.e. differences on the semantic of this
keyword).
Java 8 Lambda Limitations
Lambda expressions (as well as anonymous classes) in Java can only access to the final (or effectively final) variables of the enclosing scope.
For example, consider the following example:
This doesn’t compile since the incrementation of myVar
prevents it from being effectively final.
JavaScript and Its Functions
Functions and lambda expressions in JavaScript use the concept of closures:
“A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created” — MDN
In fact, the previous example works well in JavaScript.
The lambda function in this example uses the changed version of myVar
.
In practice, in JavaScript, a new function maintains a pointer to the enclosing scope where it has been defined. This fundamental mechanism allows the creation of closures that saves the storage location of free variables — those can be modified by the function itself as well as by others.
Does Java Create Closures?
Java only saves the value of free variables to let them be used inside lambda expressions. Even if there was an increment of myVar
, the lambda function would still return 42. The compiler avoids the creation of those incoherent scenarios, limiting the type of variables that can be used inside lambda expressions (and anonymous classes) to only final and effectively final ones.
Despite this limitation, we can state that Java 8 implements closures. In fact, closures, in their more theoretical acceptation, capture only the value of free variables. In pure functional languages, this is the only thing that should be allowed, keeping the referential transparency property.
Later on, some functional languages, as well as languages such as Javascript, introduced the possibility of capturing the storage locations of free variables. This allows the possibility of introducing side effects.
Said so, we could state that with JavaScript’s closures we can do more. But, how those side effects really help JavaScript? Are they really important?
Side Effects and JavaScript
To better understand the concept of closures, consider now the following JavaScript code (forgive that in JavaScript, this can be done in a very compact way, but I want it to look like Java for a comparison):
Each time createCounter is called, it creates a map with two new lambda functions, which, respectively, returns and increments the variable’s value that has been defined in the enclosing scope.
In other words, the first function has side effects that change the result of the other.
An important fact to notice here is that createCounter
’s scope still exists after its termination, — and is concurrently used by the two lambda functions.
Side Effects and Java
Now let’s try to do the same thing in Java:
This code doesn’t compile because the second lambda function is trying to change the variable count
.
Java stores function variables (e.g. count
) in the stack; those are removed with the termination of createCounter
. The created lambdas use copied versions of count
. If the compiler allowed the second lambda to change its copied version of count
, it would be quite confusing.
To support this type of closures, Java should save the enclosing scopes in the heap to let them survive after the function’s termination.
Java Closures Using Mutable Objects
As we have seen, the value of a used variable is copied to the lambda expression (or anonymous class). But, what if we used objects? In this case, only the reference would be copied, and we could look at things a little bit differently.
We could pretty much emulate the behavior of JavaScript’s closures in the following way:
Indeed, this is not really something useful, and it is truly inelegant.
Closures as a Mechanism to Create Objects
When will you learn? Closures are a poor man’s object. — Anton
Closures are used by JavaScript as a fundamental mechanism to create “class” instances: objects. This is why, in JavaScript, a function like MyCounter
is called a “constructor function.”
On the contrary, Java already has classes, and we can create objects in a much more elegant way.
In the previous example, we don’t need a closure. That “factory function” is essentially a weird example of a class definition. In Java, we can simply define a class like the following one:
Modifying Free Variables Is a Bad Practice
Lambda functions that modify free variables (i.e. any object that has been defined outside the lambda function) could generate confusion. Side effects of other functions could lead to unwanted errors.
This is typical of older languages’ developers who don’t understand why JavaScript produces random inexplicable behaviors. In functional languages, it is usually limited, and when it is not, discouraged.
Consider you are using a parallel paradigm, for example in Spark:
Conclusions
We have seen a very brief introduction of Java 8 lambda expressions. We focused on the differences between anonymous classes and lambda expressions. After that, we better saw the concept of closures, looking how they are implemented in JavaScript. Furthermore, we saw how JavaScript’s closures can’t be directly used in Java 8 and how it can be simulated by object references.
We also discovered that Java has a limited support for closures when we compare them with languages such as JavaScript.
Nevertheless, we saw how those limitations are not really important. In fact, closures are used in JavaScript as the fundamental mechanism to define classes and create objects, and we all know that it is not a Java problem.