Статьи

(Ab) использование Java 8 FunctionalInterfaces в качестве локальных методов

Если вы программируете на более продвинутых языках, таких как Scala, Ceylon или даже JavaScript, «вложенные функции» или «локальные функции» являются для вас очень распространенной идиомой. Например, вы напишите такие вещи, как функции Фибоначчи:

1
2
3
4
def f() = {
  def g() = "a string!"
  g() + "– says g"
}

Функция f() содержит вложенную функцию g() , которая является локальной для области видимости внешней f() .

В Java нет способа создать локальную функцию, подобную этой, но вы можете назначить лямбда-выражение локальной переменной и использовать ее вместо этого.

Приведенный выше пример может быть переведен в следующий код Java:

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

Хотя этот пример довольно тривиален, гораздо более полезным вариантом использования является тестирование. Например, рассмотрим следующий модульный тест jOOλ , который проверяет, правильно ли Stream.close() семантика Stream.close() во всех видах методов jOOλ Seq , которые объединяют два потока в один:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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));
}

Локальная функция является test и принимает два аргумента Stream<Integer> , что приводит к результату Seq<?> .

Почему бы просто не написать приватный метод?

Конечно, это можно было бы решить и с помощью частного метода, классического стиля Java. Но иногда использование локальной области видимости гораздо удобнее, так как тест Consumer (локальная функция) не выходит за рамки этого одиночного модульного теста. Он должен использоваться только в этом единственном методе.

Альтернативным, более классическим способом Java было бы вместо этого определить локальный класс и поместить в него функцию. Но это решение гораздо более бережное.

Однако один недостаток заключается в том, что рекурсию гораздо сложнее реализовать в Java.