DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Immutability in JavaScript — When and Why Should You Use It
  • Writing DTOs With Java8, Lombok, and Java14+
  • An Introduction to Object Mutation in JavaScript
  • Metaprogramming With Proxies and Reflect in JavaScript

Trending

  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  • Emerging Data Architectures: The Future of Data Management
  • Failure Handling Mechanisms in Microservices and Their Importance
  • *You* Can Shape Trend Reports: Join DZone's Software Supply Chain Security Research
  1. DZone
  2. Coding
  3. Languages
  4. Java 8 Lambda Limitations: Closures

Java 8 Lambda Limitations: Closures

Java doesn't rely on closures the way functional programming languages and JavaScript do, but they're a loose thread in the implementation of lambda expressions.

By 
Miro Mannino user avatar
Miro Mannino
·
Feb. 20, 17 · Tutorial
Likes (32)
Comment
Save
Tweet
Share
90.3K Views

Join the DZone community and get the full member experience.

Join For Free

Suppose we want to create a simple thread that only prints something on the console:

int answer = 42;
Thread t = new Thread(
  () -> System.out.println("The answer is: " + answer)
);


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:

int answer = 42;
Thread t = new Thread(new Runnable() {
    public void run() {
        System.out.println("The answer is: " + answer);
    }
});


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:

void fn() {
    int myVar = 42;
    Supplier<Integer> lambdaFun = () -> myVar; // error
    myVar++;
    System.out.println(lambdaFun.get());
}


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.

function fn() { // the enclosing scope
    var myVar = 42;
    var lambdaFun = () => myVar;
    myVar++;
    console.log(lambdaFun()); // it prints 43
}


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):

function createCounter(initValue) { // the enclosing scope
    var count = initValue;
    var map = new Map();
    map.set('val', () => count);
    map.set('inc', () => count++);
    return map;
}

v = createCounter(42);
v.get('val')(); // returns 42
v.get('inc')(); // returns 42
v.get('val')(); // returns 43


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:

public static Map<String, Supplier> createCounter(int initValue) { // the enclosing scope
    int count = initValue;
    Map<String, Supplier> map = new HashMap<>();
    map.put("val", () -> count);
    map.put("inc", () -> count++);
    return map;
}


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:

private static class MyClosure {
    public int value;
    public MyClosure(int initValue) { this.value = initValue; }
}
public static Map<String, Supplier> createCounter(int initValue) {
    MyClosure closure = new MyClosure(initValue);
    Map<String, Supplier> counter = new HashMap<>();
    counter.put("val", () -> closure.value);
    counter.put("inc", () -> closure.value++);
    return counter;
}

Supplier[] v = createCounter(42);
v.get("val").get(); // returns 42
v.get("inc").get(); // returns 42
v.get("val").get(); // returns 43


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:

class MyJavaCounter {
    private int value;
    public MyJavaCounter(int initValue) { this.value = initValue; }
    public int increment() { return value++; }
    public int get() { return value; }
}

MyJavaCounter v = new MyJavaCounter(42);
System.out.println(v.get());       // returns 42
System.out.println(v.increment()); // returns 42
System.out.println(v.get());       // returns 43


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:

int counter = 0;
JavaRDD rdd = sc.parallelize(data);
rdd.foreach(x -> counter += x); // Don't do this!!

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.

Java (programming language) JavaScript Object (computer science)

Published at DZone with permission of Miro Mannino. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Immutability in JavaScript — When and Why Should You Use It
  • Writing DTOs With Java8, Lombok, and Java14+
  • An Introduction to Object Mutation in JavaScript
  • Metaprogramming With Proxies and Reflect in JavaScript

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!