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

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

1. обзор

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

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

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

Большая часть функций библиотеки вращается вокруг интерфейсаF. This F interface models a function that takes an input of type A and returns an output of type B. Все это построено на основе собственной системы типов Java.

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

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


    org.functionaljava
    functionaljava
    4.8.1


    org.functionaljava
    functionaljava-java8
    4.8.1


    org.functionaljava
    functionaljava-quickcheck
    4.8.1


    org.functionaljava
    functionaljava-java-core
    4.8.1

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

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

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

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

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

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

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

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

public static final F isEven = i -> i % 2 == 0;

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

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

Функциональная библиотека Java предоставляет обычный набор типов для управления данными, такими как списки, наборы, массивы и карты. The key thing to realize is that these data types are immutable.с

Кроме того, при необходимости библиотека предоставляетconvenience functions to convert to and from standard Java Collections classes.

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

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

    assertTrue(fList1.equals(fList2));
}

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

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

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

    assertTrue(evenList.equals(evenListTrueResult));
}

Since the map method returns a list, we can apply another function to its output. Порядок, в котором мы вызываем наши функцииmap, изменяет наш результирующий вывод:

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

    assertFalse(fList1.equals(fList2));
}

Вывод вышеперечисленных списков будет:

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

6. Фильтрация с использованием функции

Еще одна часто используемая операция в функциональном программировании -take an input and filter out data based on some criteria. Как вы уже, наверное, догадались, эти критерии фильтрации представлены в виде функции. Эта функция должна будет возвращать логическое значение, чтобы указать, нужно ли включать данные в вывод.

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

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

    assertTrue(filteredArray.equals(result));
}

Одно интересное наблюдение заключается в том, что в этом примере мы использовалиArray вместоList, как мы использовали в предыдущих примерах, и наша функция работала нормально. Because of the way functions are abstracted and executed, they do not need to be aware of what method was used to collect the input and output.

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

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

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

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

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

    Array array2 = Array.array("Welcome", "To", "example");
    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 - это функция, которая возвращает истину, если символ в нижнем регистре. Таким образом, применениеforall(Characters.isLowerCase) к списку символов вернет толькоtrue, если весь список состоит из символов нижнего регистра, что, в свою очередь, указывает на то, что исходная строка была полностью строчной.

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

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

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

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

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

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

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

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

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

The Functional Java library refers to this functionality as folding.

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

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

public void foldLeft_givenArray_returnResult() {
    Array 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. Заключение

Эта статья - только краткое введение в Функциональную библиотеку Java.

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