(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.
Reference: | (Ab)using Java 8 FunctionalInterfaces as Local Methods from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog. |