Функциональный стиль в java: когда он нужен?



Большинство задач в энтерпрайзе делятся на две группы:

1️⃣ Одиночный запрос.

Покупка в интернет-магазине, вывод курса доллара — такие запросы затрагивают мало данных и много компонентов. Эти бизнес-процессы хорошо описываются объектами и отношениями между ними.



2️⃣ Обработка данных.

Статистика покупок, поиск рекомендаций, прогноз погоды - в центре внимания обработка данных, а не взаимодействие сущностей. Задача разработчика — чтобы обработка была быстрой. Нужно по максимуму использовать ресурсы процессора и доступную память, снизить время простоя и количество блокировок.



Вот здесь пригодятся некоторые подходы функционального программирования(ФП) :

1️⃣ Понятие чистой функции.

В многопоточном программировании главная проблема — работа с общими переменными. Чтобы безопасно их обновлять и всегда читать актуальные знвчения используются средства синхронизации. Большинство из них снижают общую пропускную способность.

Чистые функции - методы, которые характеризуются только входными и выходными данными. У них нет сайд-эффектов, Они не меняют внешних объектов и не бросают эксепшены. Для одинаковых входных данных результат всегда будет один и тот же.

Нет общих переменных — нет синхронизации.

Можно кэшировать некоторые результаты.



2️⃣ Неизменяемые данные

Исходные данные не должны меняться. Меняться могут только локальные переменные.

Синхронизация не нужна — общие переменные не меняются и всегда актуальны.



Пример: добавление элемента в список.

Подход ООП: добавляем в исходный список - меняем внешний объект.

public void add(List list, Integer value)

{ list.add(value); }

Минимальный расход памяти.

Для запуска в многопоточной среде нужна синхронизация.

Если метод прервался, то нужно выяснять, что он успел изменить, а что нет.



Подход ФП: создаём новый список на основе исходного и добавляем туда элемент.

public List add(List list, Integer value {

List newList=new List();

newList.addAll(list);

newList.add(value);

return list;

}

Можно использовать в многопоточной среде.

Если что-то пошло не так, просто ещё раз вызываем метод.

Большой расход памяти.



3️⃣ Декларативный стиль написания кода.

Обход коллекции в цикле — это императивный подход. Для каждого элемента задаётся чёткая последовательность действий.

Stream API — декларативный. Благодаря большому количеству встроенных функций можно просто описать то, что нужно: отфильтровать, сгруппировать, просуммировать.

Лучшая читаемость - не единственное преимущество такого подхода. Чистые функции и неизменяемые данные сами по себе тоже не сделают программу эффективной. Но их использование в Stream API дают компилятору и JVM большие возможности для оптимизаций. Cамая мощная из них — опция parallel(), она автоматически распределяет данные по параллельным подзадачам.



Начиная с 8 версии java можно применять и другие идеи ФП — функции высших порядков, currying, pattern matching и т.д. Некоторые подходы были доступны и раньше, например, использовать хвостовую рекурсию вместо итерации. Но наибольшую практическую пользу можно извлечь из обработки больших коллекций с помощью Stream API.



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