Статьи

Остерегайтесь функционального программирования на Java!

Это не будет громкой болтовней о функциональном программировании, и это здорово. Это предупреждение о некоторых приемах, которые вы, вероятно, собираетесь применить к своему коду, которые ужасно неверны! ,

Функции высшего порядка необходимы для функционального программирования, и поэтому их обсуждение поможет вам быть в центре внимания на вечеринках.

Если вы пишете JavaScript, вы делаете это все время. Например:

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

Вышеуказанная функция setTimeout() является функцией высшего порядка. Это функция, которая принимает анонимную функцию в качестве аргумента. Через 10 секунд он вызовет функцию, переданную в качестве аргумента.

Мы можем написать еще одну простую функцию более высокого порядка, которая в результате предоставляет вышеуказанную функцию:

1
2
3
4
5
6
7
var message = function(text) {
    return function() {
        alert(text);
    }
};
 
setTimeout(message('10 Seconds passed'), 10000);

Если вы выполните вышеизложенное, message() будет выполняться, возвращая анонимную функцию, которая предупреждает текст аргумента, который вы передали message()

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

Почему эта практика коварна на Яве?

По тем же причинам. «Функция» (лямбда-выражение), возвращаемая из «функции» (метода) более высокого порядка, будет захватывать внешнюю область и способна воздействовать на эту область при вызове.

Самый тривиальный пример приведен здесь:

01
02
03
04
05
06
07
08
09
10
11
12
class Test {
    public static void main(String[] args) {
        Runnable runnable = runnable();
        runnable.run(); // Breakpoint here
    }
 
    static Runnable runnable() {
        return () -> {
            System.out.println("Hello");
        };
    }
}

В приведенной выше логике, если вы установите runnable.run() останова там, где выполняется runnable.run() , вы можете увидеть безвредный лямбда-экземпляр в стеке. Простой сгенерированный класс, поддерживающий реализацию функционального интерфейса:

безвредна лямбда-экземпляр

Теперь давайте переведем этот пример в обычное приложение Enterprise ™ ( обратите внимание на аннотации ), которое мы значительно упростили, чтобы соответствовать этому сообщению в блоге:

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

Точка останова остается на том же месте. Что мы видим в стеке?

Все еще безобидный маленький экземпляр лямбды:

безвредны лямбда-экземпляра-2

Хорошо. Конечно. Давайте добавим некоторые дополнительные журналы, просто для отладки

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

По электронной почте Ой!

Внезапно «безобидная» небольшая this ссылка вынудила компилятор Java заключить включающий экземпляр EnterpriseBean™ в возвращенный класс Runnable :

предательская-лямбда-с-вмещающих инстанции

И вместе с этим появился тяжелый enterpriseStateObject , который больше не может быть сборщиком мусора, пока сайт вызова не выпустит безобидный маленький Runnable

ОК, сейчас ничего нового, не так ли?

На самом деле это не так. Java 8 не имеет функций первого класса, и это нормально. Идея подкрепления лямбда-выражений номинальными типами SAM довольно хитрая, поскольку она позволяет обновлять и лямбда-и-фу все существующие библиотеки в экосистеме Java без их изменения.

Кроме того, с анонимным классом, вся эта история не была бы удивительной. Следующий стиль кодирования утек внутреннее состояние через анонимные классы со времен старого доброго стиля Swing 1.0 ActionListener et al.

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

Что нового? Лямбда-стиль будет поощрять использование повсюду функций высшего порядка в Java. Что в целом хорошо. Но только тогда, когда функция высшего порядка является статическим методом, чьи результирующие типы не будут заключать в себе никакого состояния.

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

Поэтому будьте осторожны и следуйте этому правилу:

(«Чистый») Функции высшего порядка ДОЛЖНЫ быть статическими методами в Java!

дальнейшее чтение

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