Home » Java » Core Java » (Ab)using Java 8 FunctionalInterfaces as Local Methods

About Lukas Eder

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.

(Ab)using Java 8 FunctionalInterfaces as Local Methods

If you’re programming in more advanced languages like Scala or Ceylon, or even JavaScript, “nested functions” or “local functions” are a very common idiom to you. For instance, you’ll write things like fibonacci functions as such:

def f() = {
  def g() = "a string!"
  g() + "– says g"
}

The f() function contains a nested g() function, which is local to the scope of the outer f() function.

In Java, there is no way to create a local function like this, but you can assign a lambda expression to a local variable and use that instead.

The above example can be translated to the following Java code:

String f() {
    Supplier<String> g = () -> "a string!";
    return g.get() + "- says g";
}

While this example is rather trivial, a much more useful use-case is testing. For instance, consider the following jOOλ unit test, which checks whether the Stream.close() semantics is properly implemented across all sorts of jOOλ Seq methods, that combine two streams into one:

@Test
public void testCloseCombineTwoSeqs() {
    Consumer<BiFunction<Stream<Integer>, Stream<Integer>, Seq<?>>> test = f -> {
        AtomicBoolean closed1 = new AtomicBoolean();
        AtomicBoolean closed2 = new AtomicBoolean();
        
        Stream s1 = Stream.of(1, 2).onClose(() -> closed1.set(true));
        Stream s2 = Stream.of(3).onClose(() -> closed2.set(true));
        
        try (Seq s3 = f.apply(s1, s2)) {
            s3.collect(Collectors.toList());
        }

        assertTrue(closed1.get());
        assertTrue(closed2.get());
    };
    
    test.accept((s1, s2) -> seq(s1).concat(s2));
    test.accept((s1, s2) -> seq(s1).crossJoin(s2));
    test.accept((s1, s2) -> seq(s1).innerJoin(s2, (a, b) -> true));
    test.accept((s1, s2) -> seq(s1).leftOuterJoin(s2, (a, b) -> true));
    test.accept((s1, s2) -> seq(s1).rightOuterJoin(s2, (a, b) -> true));
}

The local function is test, and it takes two Stream<Integer> arguments, producing a Seq<?> result.

Why not just write a private method?

Of course, this could have been solved with a private method as well, classic Java style. But sometimes, using a local scope is much more convenient, as the test Consumer (local function) does not escape the scope of this single unit test. It should be used only within this single method.

An alternative, more classic Java way would have been to define a local class, instead, and put the function inside of that. But this solution is much more lean.

One disadvantage, however, is that recursion is much harder to implement this way, in Java.

(0 rating, 0 votes)
You need to be a registered member to rate this.
Start the discussion Views Tweet it!
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy
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