Функциональное программирование в Java
Что такое функциональное программирование? Если в двух словах, то функциональное программирование — это программирование, в котором функции являются объектами, и их можно присваивать переменным, передавать в качестве аргументов другим функциям, возвращать в качестве результата от функций и т. п. Преимущества, которые раскрывает такая возможность, будут понятны чуть позже. Пока нам надо разобраться, как в Java можно использовать саму конструкцию «функция».
Как известно, в Java нету функций, там есть только классы, методы и объекты классов. Зато в Java есть анонимные классы, то есть классы без имени, которые можно объявлять прямо в коде любого метода. Этим мы и воспользуемся. Для начала объявим такой интерфейс:
Теперь в коде какого-нибудь метода мы можем объявить анонимную реализацию этого интерфейса:
Такую реализацию мы и будем называть «анонимной функцией». С точки зрения функционального программирования с ней можно делать все то же самое, что и с функцией из функциональных языков: присваивать переменным, передавать в качестве аргумента другим функциям(и методам классов), получать в качестве результата от функций(и методов классов).
Теперь можно перейти к изложению некоторых базовых паттернов функционального программирования.
Работа с коллекциями в функциональном стиле
Допустим, у нас есть некая коллекция целых чисел. Мы хотим их вывести на экран в виде строки, и каждое число в строке будет разделено через запятую. Нефункциональное решение выглядело бы примерно так:
Для реализации функционального решения нам потребуется сперва подготовить несколько функций и методов. Будем объявлять их в качестве статических полей класса:
Теперь наш метод joinNumbers будет выглядить следующим образом:
Метод реализован ровно в одну простую строку.
- Методы map и join являются достаточно обобщенными, то есть их можно применять не только для решения данной задачи. Это значит, что их можно было бы выделить в некий утилитный класс, и использовать потом этот класс в разных частях проекта.
- Вместо класса Collection в методе map можно было бы передавать Iterable и возвращать новый Iterable , извлекая из переданной коллекции данные по мере обхода данных в возвращаемой коллекции, то есть извлекать элементы лениво, поэтапно, а не все сразу. Такая реализация, позволит, например, создавать цепочки преобразования данных, выделяя каждый этап преобразования в отдельную простую функцию, при этом эффективность алгоритма будет оставаться порядка O(n):
map(map(numbers, MULTIPLY_X_2), INT_TO_STRING); // каждый элемент умножаем на два и приводим к строке. - Создавая какой-нибудь класс, вы можете создавать для некоторых его методов статические поля, являющиеся функциями-обертками, делегирующими вызов apply на вызов соответствующего метода класса. Это позволит использовать «методы» объектов в функциональном стиле, например, в представленных выше конструкциях.
Работа с коллекциями с помощью Google Collections
- 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 конечно есть еще много других полезных вещей как для функционального программирования, так и для работы с коллекциями в императивном стиле.
An Introduction to Functional Programming in Java 8: Part 1 — Functions as Objects
After you’ve read in Part 0 why functional programming is cool, we will make our first big steps into this topic today. In Java 8, functions became first class. Therefore, you can take functions as arguments of other functions, return functions and store functions as objects.
Why should you store a function as an object?
1. Make “super private” functions
As you know, code quality is important. That’s the reason we use private functions to reduce the methods one object of a class has. We don’t want to show the underlying code to others, they just have to work with some public methods on the object. But what if we want to create functions in our class which are visible to just one method and invisble to the rest of the class? With functions as first class objects, We can store the function in a single object, which can be seen just in this very one method.
2. Upgrade Design Patterns
If you ever worked with on a big software project you know how messy it can become. That’s the reason design patterns were invented. One of the cooler ones is the strategy pattern. I will write a detailed post about it later on, but what it basically does is to switch similar algorithms, depending on a parameter. For each algorithm, you have to write your own class, which implements a function from an interface. But when you can store functions in an object, you just need one “function object” for each algorithm. That makes the code much clearer and smaller.
3. Create “higher-order” functions
Now comes the fun part. You can use every object as a parameter of a method. So why not call a method with a function argument? Methods which take a function as a parameter or return one, are called higher-order functions. Before I can give you an example we have to learn how to store a function in an object.
Storing a function in an Object
In Java 8, the java.util.Function<T, R> Interface was introduced. It can store a function which takes one argument and returns an object. The Generic T is the type of the argument and R the type of the object you return.
Example: compute function
This is a very easy example of a higher-order function. It takes a function and an Integer and computes the given function with the Integer.
And now, we want to use this function to invert an Integer
There are two interesting lines here. The first one is:
The apply method of a Function object just takes an arguments and returns the method’s output. In our example, you could also write
The second interesting line is this one:
What we use here is a so called method reference. We make the function invert() to a Function object by using the :: operator. It is one of two ways to store a function in an object. But this code doesn’t make anything easier. You could refactor it like this:
It doesn’t need an compute function or any fP at all, but it still looks nicer. To make our fP code useful, we have to introduce our second way to store a function in an object. And this is by using anonymous functions, or so called Lambdas.
How to work with Lambdas
To work with Lambdas in Java 8, we have to look at a new syntax to handle them properly.
Example: Adding Two Integers
In good old Java 7, you can write a method to add two Integers like this:
And this is a Java 8 Lambda which does exactly the same:
That’s pretty straightforward, isn’t it? BiFunction is another Interface in java.util to represent a function with two arguments and one return object. In the brackets of the Lambda, you define the arguments. You don’t have to give them a type, you just have to say how many there are and how each should be called. This is equivalent to
in the Java 7 method. Next off, you have the “->” arrow. It is equivalant to the curly brackets and seperates the function’s head from its body And after the arrow, you can work with the arguments. If you have just one calculation to make, return isn’t necessary because it returns the result of this line. You can also make the function’s body bigger by using currly brackets.
But most of the times, you just need one line and therefore no brackets and no return keyword.
Making the compute Function Nicer
With that in mind, we can refactor our previous use of the compute function.
Now that’s some beautiful code! We can make our invert function to a Lambda. This makes the code nicer than our old fP version and the Java 7 example. We don’t have to create an extra method to invert the Integer, we just have to use a small lambda which does the work.
You might ask yourself now why we don’t just return -value . And in this case, it would be better. However, if we want to change the function at runtime, our version can be used for this. Thanks to kdm06 for pointing this out.
Conclusion
That’s it for today! We have made our first big steps towards functional programming in Java 8. First off, we have seen a lot of benefits of fP. After that, we have used our first function as an argument in another method by using method references and Lambdas(anonymous functions).
In part 2, we will introduce Optionals and how we can work with them properly.
Функциональное программирование в Java: определение, паттерны и применение
Как функциональное программирования в Java выглядит на деле. Для начала давайте создадим вот такой интерфейс:
public finish interface MyFunction
<
R apply(P form);
>
Потом возьмем и объявим анонимную реализацию выше описанного интерфейса. Например так:
public static void main() <
// Объявим «анонимную функцию», присвоив ее значение переменной intAndString.
MyFunction intAndString = new MyFunction() <
@Override public MyString apply(Integer from) <
return from.AndString();
>
>;
intAndString.apply(8000); // Происходит вызов нашей «анонимной функции», где в ответ получим строку «8000».
>
Чуть выше мы показали, как реализовывается «анонимная функция». Если рассматривать ее с позиции функционального программирования, тогда она ничем не отличается от обычной функции функциональных языков программирования. То ест ь с ней можно делать все, что обычно делают программисты с функциями. Таким образом Java «превращается» из объектно-ориентированного языка в функциональный язык программирования.
Функциональное программировани е в Java: принципы
-
языки, которые специально спроектированы для реализации функциональной парадигмы, например : Haskell, Erlang, F# и др. ;
-
языки, которые поддерживают возможности объектно-ориентированного и функционального программирования, например : Java, JavaScript, Python, PHP, C++ и др. ;
-
языки, которые не поддерживают реализацию функционального программирования.
-
Переменные и функции. Это важнейшие составляющие функциональной парадигмы. С переменными в Java все в порядке, а вот с функциями нужно повозиться и реализовывать их через «анонимные» интерфейсы и методы.
-
Чистые функции. Такие функции не создают проблемных эффектов и при идентичном входящем значении всегда выдают одинаковый вывод. Благодаря «чистым» функциям снижается риск возникновения ошибок в программе.
-
Неизменяемые данные и состояния. После их определения данны е или состояни я н е могут видоизменяться. Благодаря этому свойству сохраняется постоянство рабочей среды для выводящих значений функции. При соблюдении этого принципа каждая функция воспроизводит один и тот же результат и не имеет зависимости от состояния программного обеспечения. Также такой принцип исключает применение функций с общим состоянием. Это когда в одно состояние программы упира е тся несколько функций.
-
Рекурсия. Это способ осуществлять перебор информации в функциональном программировании без использования цикла «if. else».
-
Первоклассность функций. Этот принцип позволяет применять функци ю к ак обычное значение. Например, можно заполнить функциями массив или сохранить их в переменной.
-
Высший порядок функции. Этот принцип позволяет одной функции использовать другую функцию в качестве своего аргумента или возвращать ее в качестве исходящего значения.
-
Композиция функций. Этот принцип подразумевает построение структуры из функций, где результат выполнения одной функции будет передаваться в другую функцию , и так дальше по цепочке. Таким образом, при помощи вызова одной функции можно спровоцировать исполнение целой цепочки функций.
Преимущество функционального программирования в Java
-
более легкая отладка за счет использования «чистых» функций и неизменных данных;
-
отложенное вычисление происходит за счет того, что функциональная программа вычисляется только при надобности;
-
модульность достигается за счет того, что «чистые» функции можно использовать в разных областях одного кода;
-
улучшенная читабельность достигается за счет того, что поведение каждой отдельной функции предсказуемо и неизменно;
-
облегченное параллельное программирование;
-
и др.
Заключение
Функциональное программирование в Java не является основной парадигмой, однако тоже довольно просто реализуется. Обычно функциональное программирование имеет смысл применять тогда, когда программные решения легко выражаются при помощи функций и не имеют тесной связи с реальным миром. ООП чаще всего реализуется, когда п рограмма моделируется с использованием объектов из реальной жизни. Подробнее на разнице между ФП и ООП мы остановимся в следующих статьях.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.
Функциональное программирование на Java
Это будет краткое введение в функциональное программирование на Java. Для того чтобы понять этот учебник, вы должны быть знакомы с основами написания кода на Java. Если вы хотите проследить за развитием событий и попробовать некоторые из кодов, вы можете использовать вашу любимую IDE; лично я использую IntelliJ.
Что такое функциональное программирование на Java?
Проще говоря, функциональное программирование — это стиль программирования, который использует функции. Эти функции можно представить как простые математические функции, например, f(x) = x + 2.
Очень распространенным описанием языка Java является то, что это объектно-ориентированный язык программирования, и любой Java-разработчик должен быть знаком с такими конструкциями ООП, как классы и объекты. В функциональном программировании основное внимание уделяется не столько объектам, сколько функциям.
Предсказуемость функционального программирования позволяет нам писать код, который легче поддается тестированию и не имеет желаемых побочных эффектов. Это происходит потому, что функции в функциональном программировании являются чистыми (они всегда возвращают один и тот же выход для заданного входа) и обеспечивают неизменяемость и ссылочную прозрачность.
Как использовать функциональное программирование в Java?
В Java функциональное программирование возможно благодаря использованию лямбд, которые были введены в Java 8. Лямбды — это реализации на функциональных интерфейсах (интерфейс с одним абстрактным методом), и именно они играют роль «функций» в функциональном программировании. Их можно присваивать переменным, передавать в качестве параметров другим функциям или даже возвращать из других функций.
Зачем нам нужно Функциональное программирование в Java?
Есть несколько причин, по которым нам следует обратить внимание на Функциональное программирование, поскольку мы стремимся писать лаконичный, читабельный и эффективный код.
- Ленивая оценка: При ленивой оценке выражение выполняется только тогда, когда требуется его значение, что экономит время и позволяет не выполнять одно и то же вычисление несколько раз. В качестве иллюстрации этого примера ниже приведен результат
- Неизменность: Функции в функциональном программировании не изменяют состояние, а действуют только на тех входах, которые им предоставляются. Это позволяет избежать неожиданного поведения и облегчает отладку кода.
- Читабельность и краткость: С помощью функционального программирования мы пишем гораздо более лаконичный код, который, как правило, более читабелен.
Это был краткий обзор функционального программирования в Java, его использования и преимуществ. Поскольку функции рассматриваются как «граждане первого класса», мы получаем возможность достичь многого и более эффективно в Java. Функциональное программирование не призвано противостоять объектно-ориентированному программированию, а действительно дополняет его, и их сочетание в ваших программах — это более очевидный путь.