«1. Обзор

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

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

Функциональная библиотека 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));
}

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

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. Фильтрация с использованием функции

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

Теперь воспользуемся нашей функцией 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. Применение булевой логики с использованием функции

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

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

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

    Array<String> array2 = Array.array("Welcome", "To", "Baeldung");
    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 предоставляет обе версии:

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

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);
}

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

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

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

«