Core Java

Beware of Functional Programming in Java!

This isn’t going to be a rant about functional programming, which is awesome. This is a warning about some practices that you are very likely going to apply to your code, which are terribly wrong!.

Higher order functions are essential to functional programming, and thus, talking about them will help you be the center of attention at parties.

If you’re writing JavaScript, you’re doing it all the time. For instance:

setTimeout(function() {
    alert('10 Seconds passed');
}, 10000);

The above setTimeout() function is a higher-order function. It is a function that takes an anonymous function as an argument. After 10 seconds, it will call the function passed as an argument.

We can write another easy higher-order function that provides the above function as a result:

var message = function(text) {
    return function() {
        alert(text);
    }
};

setTimeout(message('10 Seconds passed'), 10000);

If you execute the above, message() will be executed, returning an anonymous function, which alerts the argument text you have passed to message()

In functional programming, the above is common practice. A function returned from a higher-order function will capture the outer scope and is able to act on this scope when called.

Why is this practice treacherous in Java?

For the same reasons. A “function” (lambda) returned from a higher-order “function” (method) will capture the outer scope and is able to act on this scope when called.

The most trivial example is given here:

class Test {
    public static void main(String[] args) {
        Runnable runnable = runnable();
        runnable.run(); // Breakpoint here
    }

    static Runnable runnable() {
        return () -> {
            System.out.println("Hello");
        };
    }
}

In the above logic, if you put a breakpoint right where the runnable.run() call is made, you can see the harmless lambda instance on the stack. A simple generated class, backing the functional interface implementation:

harmless-lambda-instance

Now let’s translate this example to your average Enterprise™ application (notice the annotations), which we’ve greatly simplified to fit this blog post:

class Test {
    public static void main(String[] args) {
        Runnable runnable = new EnterpriseBean()
            .runnable();
        runnable.run(); // Breakpoint here
    }
}

@ImportantDeclaration
@NoMoreXML({
    @CoolNewValidationStuff("Annotations"),
    @CoolNewValidationStuff("Rock")
})
class EnterpriseBean {
    Object[] enterpriseStateObject = 
        new Object[100_000_000];

    Runnable runnable() {
        return () -> {
            System.out.println("Hello");
        };
    }
}

The breakpoint is still at the same spot. What do we see on the stack?

Still a harmless little lambda instance:

harmless-lambda-instance-2

Fine. Of course. Let’s add some additional logging, just for debugging

class Test {
    public static void main(String[] args) {
        Runnable runnable = new EnterpriseBean()
            .runnable();
        runnable.run(); // Breakpoint here
    }
}

@ImportantDeclaration
@NoMoreXML({
    @CoolNewValidationStuff("Annotations"),
    @CoolNewValidationStuff("Rock")
})
class EnterpriseBean {
    Object[] enterpriseStateObject = 
        new Object[100_000_000];

    Runnable runnable() {
        return () -> {
            // Some harmless debugging here
            System.out.println("Hello from: " + this);
        };
    }
}

Ooops!

Suddenly, the “harmless” little this reference forced the Java compiler to enclose the enclosing instance of the EnterpriseBean™ in the returned Runnable class:

treacherous-lambda-with-enclosing-instance

And with it that heavy enterpriseStateObject came along, which can now no longer be garbage collected, until the call site releases the harmless little Runnable

OK, this is nothing new now, is it?

Indeed, it isn’t. Java 8 doesn’t have first-class functions, and that’s OK. The idea of backing lambda expressions by nominal SAM types is quite cunning, as it allowed to upgrade and lambda-y-fy all existing libraries in the Java ecosystem without changing them.

Also, with an anonymous class, this whole story would not have been surprising. The following coding style has leaked internal state via anonymous classes since good old Swing 1.0 style ActionListener et al.

class Test {
    public static void main(String[] args) {
        Runnable runnable = new EnterpriseBean()
            .runnable();
        runnable.run();
    }
}

@ImportantDeclaration
@NoMoreXML({
    @CoolNewValidationStuff("Annotations"),
    @CoolNewValidationStuff("Rock")
})
class EnterpriseBean {
    Object[] enterpriseStateObject = 
        new Object[100_000_000];

    Runnable runnable() {
        return new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from " + this);
            }
        };
    }
}

What’s new? The lambda style will encourage using higher-order functions in Java, all over the place. Which is generally good. But only when the higher-order function is a static method, whose resulting types will not enclose any state.

With the above examples, however, we can see that we’re going to be debugging through a couple of memory leaks and problems in the near future, when we start embracing Java 8’s functional style programming.

So, be careful, and follow this rule:

(“Pure”) Higher order functions MUST be static methods in Java!

Further reading

Enclosing instances have caused issues before. Read about how the dreaded double curly braces anti pattern has caused pain and suffering among Java developers for the last two decades.

Lukas Eder

Lukas is a Java and SQL enthusiast developer. He created the Data Geekery GmbH. He is the creator of jOOQ, a comprehensive SQL library for Java, and he is blogging mostly about these three topics: Java, SQL and jOOQ.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button