Перейти к основному содержимому

Введение в функциональную Java

· 6 мин. чтения

1. Обзор

В этом руководстве мы предоставим краткий обзор функциональной библиотеки Java вместе с несколькими примерами.

2. Функциональная библиотека Java

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

Большая часть функциональности библиотеки связана с F - интерфейсом. Этот F -интерфейс моделирует функцию, которая принимает входные данные типа A и возвращает выходные данные типа B. Все это построено поверх собственной системы типов Java.

3. Зависимости Maven

Во-первых, нам нужно добавить необходимые зависимости в наш файл pom.xml :

<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava</artifactId>
<version>4.8.1</version>
</dependency>
<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava-java8</artifactId>
<version>4.8.1</version>
</dependency>
<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava-quickcheck</artifactId>
<version>4.8.1</version>
</dependency>
<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava-java-core</artifactId>
<version>4.8.1</version>
</dependency>

4. Определение функции

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

Без функциональной Java базовый метод умножения выглядел бы примерно так:

public static final Integer timesTwoRegular(Integer i) {
return i * 2;
}

Используя функциональную библиотеку Java, мы можем определить эту функциональность немного более элегантно:

public static final F<Integer, Integer> timesTwo = i -> i * 2;

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

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

public static final F<Integer, Boolean> isEven = i -> i % 2 == 0;

5. Применение функции

Теперь, когда у нас есть наши функции, давайте применим их к набору данных.

Библиотека Functional Java предоставляет обычный набор типов для управления такими данными, как списки, наборы, массивы и карты. Главное, что нужно понять, это то, что эти типы данных являются неизменяемыми.

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

В приведенном ниже примере мы определим список целых чисел и применим к нему нашу функцию timesTwo . Мы также будем вызывать карту , используя встроенное определение той же функции. Конечно, мы ожидаем, что результаты будут такими же:

public void multiplyNumbers_givenIntList_returnTrue() {
List<Integer> fList = List.list(1, 2, 3, 4);
List<Integer> fList1 = fList.map(timesTwo);
List<Integer> fList2 = fList.map(i -> i * 2);

assertTrue(fList1.equals(fList2));
}

Как мы видим, карта возвращает список того же размера, где значение каждого элемента является значением входного списка с примененной функцией. Сам список ввода не меняется.

Вот аналогичный пример с использованием нашей функции isEven :

public void calculateEvenNumbers_givenIntList_returnTrue() {
List<Integer> fList = List.list(3, 4, 5, 6);
List<Boolean> evenList = fList.map(isEven);
List<Boolean> evenListTrueResult = List.list(false, true, false, true);

assertTrue(evenList.equals(evenListTrueResult));
}

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

public void applyMultipleFunctions_givenIntList_returnFalse() {
List<Integer> fList = List.list(1, 2, 3, 4);
List<Integer> fList1 = fList.map(timesTwo).map(plusOne);
List<Integer> fList2 = fList.map(plusOne).map(timesTwo);

assertFalse(fList1.equals(fList2));
}

Вывод вышеуказанных списков будет:

List(3,5,7,9)
List(4,6,8,10)

6. Фильтрация с помощью функции

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

Теперь воспользуемся нашей функцией isEven для фильтрации нечетных чисел из входного массива с помощью метода filter :

public void filterList_givenIntList_returnResult() {
Array<Integer> array = Array.array(3, 4, 5, 6);
Array<Integer> filteredArray = array.filter(isEven);
Array<Integer> result = Array.array(4, 6);

assertTrue(filteredArray.equals(result));
}

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

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

7. Применение булевой логики с помощью функции

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

Функциональная библиотека Java предоставляет нам ярлыки для этой логики с помощью методов exists и forall :

public void checkForLowerCase_givenStringArray_returnResult() {
Array<String> array = Array.array("Welcome", "To", "foreach");
assertTrue(array.exists(s -> List.fromString(s).forall(Characters.isLowerCase)));

Array<String> array2 = Array.array("Welcome", "To", "ForEach");
assertFalse(array2.exists(s -> List.fromString(s).forall(Characters.isLowerCase)));

assertFalse(array.forall(s -> List.fromString(s).forall(Characters.isLowerCase)));
}

В приведенном выше примере мы использовали массив строк в качестве входных данных. Вызов функции fromString преобразует каждую из строк из массива в список символов. К каждому из этих списков мы применили forall(Characters.isLowerCase) .

Как вы, наверное, догадались, Characters.isLowerCase — это функция, которая возвращает true, если символ в нижнем регистре. Таким образом, применение forall(Characters.isLowerCase) к списку символов вернет true только в том случае, если весь список состоит из символов нижнего регистра, что, в свою очередь, указывает на то, что исходная строка была полностью строчной.

В первых двух тестах мы использовали exists, потому что нам нужно было знать только, была ли хотя бы одна строка строчной. Третий тест использовал forall для проверки того, все ли строки были строчными.

8. Обработка необязательных значений с помощью функции

Для обработки необязательных значений в коде обычно требуются проверки == null или isNotBlank . Java 8 теперь предоставляет класс Optional для более элегантной обработки этих проверок, а библиотека Functional Java предлагает аналогичную конструкцию для изящной обработки отсутствующих данных через свой класс Option :

public void checkOptions_givenOptions_returnResult() {
Option<Integer> n1 = Option.some(1);
Option<Integer> n2 = Option.some(2);
Option<Integer> n3 = Option.none();

F<Integer, Option<Integer>> function = i -> i % 2 == 0 ? Option.some(i + 100) : Option.none();

Option<Integer> result1 = n1.bind(function);
Option<Integer> result2 = n2.bind(function);
Option<Integer> result3 = n3.bind(function);

assertEquals(Option.none(), result1);
assertEquals(Option.some(102), result2);
assertEquals(Option.none(), result3);
}

9. Сокращение набора с помощью функции

Наконец, мы рассмотрим функциональные возможности сокращения множества. «Сокращение набора» — это причудливый способ сказать «свернуть его в одно значение».

В функциональной библиотеке Java эта функциональность называется сворачиванием .

Необходимо указать функцию, указывающую, что значит свернуть элемент. Примером этого является функция Integers.add для отображения целых чисел в массиве или списке, которые необходимо добавить.

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

public void foldLeft_givenArray_returnResult() {
Array<Integer> intArray = Array.array(17, 44, 67, 2, 22, 80, 1, 27);

int sumAll = intArray.foldLeft(Integers.add, 0);
assertEquals(260, sumAll);

int sumEven = intArray.filter(isEven).foldLeft(Integers.add, 0);
assertEquals(148, sumEven);
}

Первый foldLeft просто складывает все целые числа. Тогда как второй сначала применит фильтр, а затем добавит оставшиеся целые числа.

10. Заключение

Эта статья является кратким введением в библиотеку Functional Java.

Как всегда, полный исходный код статьи доступен на GitHub .