Эта статья о:
Если вы программируете на языках Java, C#, C++, PHP, или любом другом ОО языке, хотели бы познакомиться с функциональным программированием, но не имеет возможности/желания изучать Haskell/Scala/Lisp/Python, — эта статья специально для вас.
Тем, кто знаком с функциональным программированием, но никогда не применял его в Java, думаю, это будет тоже интересно.
Что такое функциональное программирование? Если в двух словах, то функциональное программирование — это программирование, в котором функции являются объектами, и их можно присваивать переменным, передавать в качестве аргументов другим функциям, возвращать в качестве результата от функций и т. п. Преимущества, которые раскрывает такая возможность, будут понятны чуть позже. Пока нам надо разобраться, как в Java можно использовать саму конструкцию «функция».
Как известно, в Java нету функций, там есть только классы, методы и объекты классов. Зато в Java есть анонимные классы, то есть классы без имени, которые можно объявлять прямо в коде любого метода. Этим мы и воспользуемся. Для начала объявим такой интерфейс:
Теперь в коде какого-нибудь метода мы можем объявить анонимную реализацию этого интерфейса:
Такую реализацию мы и будем называть «анонимной функцией». С точки зрения функционального программирования с ней можно делать все то же самое, что и с функцией из функциональных языков: присваивать переменным, передавать в качестве аргумента другим функциям(и методам классов), получать в качестве результата от функций(и методов классов).
Теперь можно перейти к изложению некоторых базовых паттернов функционального программирования.
Допустим, у нас есть некая коллекция целых чисел. Мы хотим их вывести на экран в виде строки, и каждое число в строке будет разделено через запятую. Нефункциональное решение выглядело бы примерно так:
Для реализации функционального решения нам потребуется сперва подготовить несколько функций и методов. Будем объявлять их в качестве статических полей класса:
Теперь наш метод joinNumbers будет выглядить следующим образом:
Метод реализован ровно в одну простую строку.
Хотелось бы отметить несколько важных моментов:
Ребята из Google как раз создали удобную библиотеку с утилитными классами, позволяющую работать с коллекциями в Java в функциональном стиле. Вот некоторые из возможностей, которые она предоставляет:
В Google Collections конечно есть еще много других полезных вещей как для функционального программирования, так и для работы с коллекциями в императивном стиле.
Дорогие друзья, если вам понравилась моя статья, я с удовольствием напишу еще что-нибудь интересное о применении функционального программирования в Java и других императивных языках. Вот некоторые из вещей, о которых есть желание рассказать, но нету возможности изложить сразу в одной статье:
Буду рад услышать ваши комментарии и предложения.
- О применении функционального стиля программирования в языке Java.
- О некоторых базовых паттернах для работы с коллекциями данных из функционального программирования в примерах на Java.
- Немного о библиотеке Google Collections.
Если вы программируете на языках Java, C#, C++, PHP, или любом другом ОО языке, хотели бы познакомиться с функциональным программированием, но не имеет возможности/желания изучать Haskell/Scala/Lisp/Python, — эта статья специально для вас.
Тем, кто знаком с функциональным программированием, но никогда не применял его в Java, думаю, это будет тоже интересно.
Вводим конструкцию «функция» в языке Java
Что такое функциональное программирование? Если в двух словах, то функциональное программирование — это программирование, в котором функции являются объектами, и их можно присваивать переменным, передавать в качестве аргументов другим функциям, возвращать в качестве результата от функций и т. п. Преимущества, которые раскрывает такая возможность, будут понятны чуть позже. Пока нам надо разобраться, как в Java можно использовать саму конструкцию «функция».
Как известно, в Java нету функций, там есть только классы, методы и объекты классов. Зато в Java есть анонимные классы, то есть классы без имени, которые можно объявлять прямо в коде любого метода. Этим мы и воспользуемся. Для начала объявим такой интерфейс:
public final interface Function<F, T> {
T apply(F from);
}
Теперь в коде какого-нибудь метода мы можем объявить анонимную реализацию этого интерфейса:
public static void main() {
// Объявляем "функцию", присваиваем ее переменной intToString.
Function<Integer, String> intToString = new Function<Integer, String>() {
@Override public String apply(Integer from) {
return from.toString();
}
};
intToString.apply(9000); // Вызываем нашу функцию. Получаем строку "9000".
}
Такую реализацию мы и будем называть «анонимной функцией». С точки зрения функционального программирования с ней можно делать все то же самое, что и с функцией из функциональных языков: присваивать переменным, передавать в качестве аргумента другим функциям(и методам классов), получать в качестве результата от функций(и методов классов).
Теперь можно перейти к изложению некоторых базовых паттернов функционального программирования.
Работа с коллекциями в функциональном стиле
Допустим, у нас есть некая коллекция целых чисел. Мы хотим их вывести на экран в виде строки, и каждое число в строке будет разделено через запятую. Нефункциональное решение выглядело бы примерно так:
public String joinNumbers(Collection<? extends Integer> numbers) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Integer number : numbers) {
if (first)
first = false;
else
result.append(", ");
result.append(number);
}
return result;
}
Для реализации функционального решения нам потребуется сперва подготовить несколько функций и методов. Будем объявлять их в качестве статических полей класса:
public static final Function<Integer, String> INT_TO_STRING = ... // Уже реализовали выше
// Берет поэлементно значения из коллекции from, преобразует их с помощью функции transformer
// и возвращает список результатов преобразования в том же порядке.
public static <F, T> List<T> map(Collection<F> from, Function<? super F,? extends T> transformer) {
ArrayList<T> result = new ArrayList<T>();
for (F element : from)
result.add(transformer.apply(element));
return result;
}
// Берет коллекцию произвольных элементов и конкатенирует их в строку
public static <T> String join(Collection<T> from, String separator) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (T element : from) {
if (first)
first = false;
else
result.append(separator);
result.append(element);
}
return result.toString();
}
Теперь наш метод joinNumbers будет выглядить следующим образом:
public String joinNumbers(Collection<? extends Integer> numbers) {
return join(map(numbers, INT_TO_STRING), ", ");
}
Метод реализован ровно в одну простую строку.
Хотелось бы отметить несколько важных моментов:
- Методы
map
иjoin
являются достаточно обобщенными, то есть их можно применять не только для решения данной задачи. Это значит, что их можно было бы выделить в некий утилитный класс, и использовать потом этот класс в разных частях проекта. - Вместо класса
Collection
в методеmap
можно было бы передаватьIterable
и возвращать новыйIterable
, извлекая из переданной коллекции данные по мере обхода данных в возвращаемой коллекции, то есть извлекать элементы лениво, поэтапно, а не все сразу. Такая реализация, позволит, например, создавать цепочки преобразования данных, выделяя каждый этап преобразования в отдельную простую функцию, при этом эффективность алгоритма будет оставаться порядка O(n):
map(map(numbers, MULTIPLY_X_2), INT_TO_STRING); // каждый элемент умножаем на два и приводим к строке.
- Создавая какой-нибудь класс, вы можете создавать для некоторых его методов статические поля, являющиеся функциями-обертками, делегирующими вызов apply на вызов соответствующего метода класса. Это позволит использовать «методы» объектов в функциональном стиле, например, в представленных выше конструкциях.
Работа с коллекциями с помощью Google Collections
Ребята из Google как раз создали удобную библиотеку с утилитными классами, позволяющую работать с коллекциями в Java в функциональном стиле. Вот некоторые из возможностей, которые она предоставляет:
interface Function<F, T>
. Интерфейс, аналогичный приведенному мной выше.Iterables.filter
. Берет коллекцию и функцию-предикат(функцию, возвращающую булево значение). В ответ возвращает коллекцию, содержающую все элементы исходной, на которые указанная функция вернула true. Удобно, например, если мы хотим отсеить из коллекции все четные числа:Iterables.filter(numbers, IS_ODD);
Iterables.transform
. Делает то же самое, что функция map в моем примере выше.Functions.compose
. Берет две функции. Возвращает новую функция — их композицию, то есть функцию, которая получает элемент, подает его во вторую функцию, результат подает в первую функцию, и полученный из первой функции результат возвращает пользователю. Композицию можно использовать, например, так:Iterables.transform(numbers, Functions.compose(INT_TO_STRING, MULTIPLY_X_2));
В Google Collections конечно есть еще много других полезных вещей как для функционального программирования, так и для работы с коллекциями в императивном стиле.
Ссылки
- Статья в Википедии о функциональном программировании.
- Google Guava, проект, частью которого является Google Collections.
- Видеопрезентация Google Collections с Joshua Bloch.
- Apache Commons Collections. Решает схожие с Google Collections задачи, но был написан под Java 4, то есть без параметрических типов.
О чем хотелось бы рассказать еще
Дорогие друзья, если вам понравилась моя статья, я с удовольствием напишу еще что-нибудь интересное о применении функционального программирования в Java и других императивных языках. Вот некоторые из вещей, о которых есть желание рассказать, но нету возможности изложить сразу в одной статье:
- Мутабельные и иммутабельные замыкания.
- Pattern-matcher.
- Монады.
- Распараллеливание с использованием функционального подхода.
- Комбинаторы парсеров.
Буду рад услышать ваши комментарии и предложения.