Введение в JOOL

Введение в JOOL

1. обзор

В этой статье мы рассмотрим библиотекуjOOL __ - еще один продукт отjOOQ.

2. Maven Dependency

Начнем с добавления зависимости Maven к вашемуpom.xml:


    org.jooq
    jool
    0.9.12

Вы можете найти последнюю версиюhere.

3. Функциональные интерфейсы

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

jOOL исправляет это, доказывая набор новых функциональных интерфейсов, которые могут принимать даже 16 параметров (отFunction1 доFunction16), и обогащены дополнительными удобными методами.

Например, чтобы создать функцию, которая принимает три аргумента, мы можем использоватьFunction3:

Function3 lengthSum
  = (v1, v2, v3) -> v1.length() + v2.length() + v3.length();

В чистой Java вам нужно будет реализовать это самостоятельно. Кроме того, функциональные интерфейсы от jOOL имеют методapplyPartially(), который позволяет нам легко выполнять частичное приложение:

Function2 addTwoNumbers = (v1, v2) -> v1 + v2;
Function1 addToTwo = addTwoNumbers.applyPartially(2);

Integer result = addToTwo.apply(5);

assertEquals(result, (Integer) 7);

Когда у нас есть метод типаFunction2, мы можем легко преобразовать его в стандартный JavaBiFunction, используя методtoBiFunction():

BiFunction biFunc = addTwoNumbers.toBiFunction();

Точно так же есть методtoFunction() в типеFunction1.

4. Кортеж

Кортеж является очень важной конструкцией в мире функционального программирования. Это типизированный контейнер для значений, где каждое значение может иметь свой тип. Tuples are often used as function arguments.

Они также очень полезны при преобразовании потока событий. В jOOL у нас есть кортежи, которые могут содержать от одного до шестнадцати значений, предоставляемых типами отTuple1 доTuple16:

tuple(2, 2)

И для четырех значений:

tuple(1,2,3,4);

Давайте рассмотрим пример, когда у нас есть последовательность кортежей, содержащих 3 значения:

Seq> personDetails = Seq.of(
  tuple("michael", "similar", 49),
  tuple("jodie", "variable", 43));
Tuple2 tuple = tuple("winter", "summer");

List> result = personDetails
  .map(t -> t.limit2().concat(tuple)).toList();

assertEquals(
  result,
  Arrays.asList(tuple("michael", "similar", "winter", "summer"), tuple("jodie", "variable", "winter", "summer"))
);

Мы можем использовать различные виды преобразований для кортежей. Сначала мы вызываем методlimit2(), чтобы получить только два значения изTuple3.. Затем мы вызываем методconcat() для объединения двух кортежей.

В результате мы получаем значения типаTuple4.

5. Seqс

КонструкцияSeq добавляет методы более высокого уровня кStream, но часто использует свои методы ниже.

5.1. Содержит операции

Мы можем найти несколько вариантов методов проверки наличия элементов вSeq.. Некоторые из этих методов используют методanyMatch() из классаStream:

assertTrue(Seq.of(1, 2, 3, 4).contains(2));

assertTrue(Seq.of(1, 2, 3, 4).containsAll(2, 3));

assertTrue(Seq.of(1, 2, 3, 4).containsAny(2, 5));

5.2. Присоединиться к операциям

Когда у нас есть два потока и мы хотим их объединить (аналогично операции соединения двух наборов данных в SQL), использование стандартного классаStream не является очень элегантным способом сделать это:

Stream left = Stream.of(1, 2, 4);
Stream right = Stream.of(1, 2, 3);

List rightCollected = right.collect(Collectors.toList());
List collect = left
  .filter(rightCollected::contains)
  .collect(Collectors.toList());

assertEquals(collect, Arrays.asList(1, 2));

Нам нужно собрать потокright в список, чтобы предотвратитьjava.lang.IllegalStateException: stream has already been operated upon or closed.. Затем нам нужно выполнить операцию побочного эффекта, обратившись к спискуrightCollected из методаfilter. Это склонный к ошибкам и не элегантный способ объединения двух наборов данных.

К счастью,Seq has useful methods to do inner, left and right joins on data sets.. Эти методы скрывают реализацию своего элегантного API.

Мы можем выполнить внутреннее соединение, используя методinnerJoin():

assertEquals(
  Seq.of(1, 2, 4).innerJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2))
);

Мы можем сделать правое и левое соединения соответственно:

assertEquals(
  Seq.of(1, 2, 4).leftOuterJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2), tuple(4, null))
);

assertEquals(
  Seq.of(1, 2, 4).rightOuterJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2), tuple(null, 3))
);

Существует даже методcrossJoin(), который позволяет выполнить декартово соединение двух наборов данных:

assertEquals(
  Seq.of(1, 2).crossJoin(Seq.of("A", "B")).toList(),
  Arrays.asList(tuple(1, "A"), tuple(1, "B"), tuple(2, "A"), tuple(2, "B"))
);

5.3. Управление aSeq

Seq имеет много полезных методов для управления последовательностями элементов. Давайте посмотрим на некоторые из них.

Мы можем использовать методcycle(), чтобы многократно брать элементы из исходной последовательности. Он создаст бесконечный поток, поэтому нам нужно быть осторожными при сборе результатов в список, поэтому нам нужно использовать методlimit() для преобразования бесконечной последовательности в конечную:

assertEquals(
  Seq.of(1, 2, 3).cycle().limit(9).toList(),
  Arrays.asList(1, 2, 3, 1, 2, 3, 1, 2, 3)
);

Допустим, мы хотим продублировать все элементы из одной последовательности во вторую последовательность. Методduplicate() делает именно это:

assertEquals(
  Seq.of(1, 2, 3).duplicate().map((first, second) -> tuple(first.toList(), second.toList())),
  tuple(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3))
);

Тип возврата методаduplicate() - это кортеж из двух последовательностей.

Допустим, у нас есть последовательность целых чисел, и мы хотим разбить эту последовательность на две последовательности, используя некоторый предикат. Мы можем использовать методpartition():

assertEquals(
  Seq.of(1, 2, 3, 4).partition(i -> i > 2)
    .map((first, second) -> tuple(first.toList(), second.toList())),
  tuple(Arrays.asList(3, 4), Arrays.asList(1, 2))
);

5.4. Группировка элементов

Группировка элементов по ключу с использованием APIStream громоздка и не интуитивно понятна, поскольку нам нужно использовать методcollect() с коллекторомCollectors.groupingBy.

Seq скрывает этот код за методомgroupBy(), который возвращаетMap, поэтому нет необходимости явно использовать методcollect():

Map> expectedAfterGroupBy = new HashMap<>();
expectedAfterGroupBy.put(1, Arrays.asList(1, 3));
expectedAfterGroupBy.put(0, Arrays.asList(2, 4));

assertEquals(
  Seq.of(1, 2, 3, 4).groupBy(i -> i % 2),
  expectedAfterGroupBy
);

5.5. Пропуск элементов

Допустим, у нас есть последовательность элементов, и мы хотим пропустить элементы, пока предикат не сопоставлен. Когда предикат удовлетворен, элементы должны попасть в результирующую последовательность.

Для этого мы можем использовать методskipWhile():

assertEquals(
  Seq.of(1, 2, 3, 4, 5).skipWhile(i -> i < 3).toList(),
  Arrays.asList(3, 4, 5)
);

Мы можем добиться того же результата, используя методskipUntil():

assertEquals(
  Seq.of(1, 2, 3, 4, 5).skipUntil(i -> i == 3).toList(),
  Arrays.asList(3, 4, 5)
);

5.6. Последовательности архивирования

Когда мы обрабатываем последовательности элементов, часто возникает необходимость объединить их в одну последовательность.

APIzip(), который можно использовать для объединения двух последовательностей в одну:

assertEquals(
  Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c")).toList(),
  Arrays.asList(tuple(1, "a"), tuple(2, "b"), tuple(3, "c"))
);

Результирующая последовательность содержит кортежи из двух элементов.

Когда мы сжимаем две последовательности, но хотим закрепить их определенным образом, мы можем передатьBiFunction методуzip(), который определяет способ сжатия элементов:

assertEquals(
  Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c"), (x, y) -> x + ":" + y).toList(),
  Arrays.asList("1:a", "2:b", "3:c")
);

Иногда полезно заархивировать последовательность с индексом элементов в этой последовательности через APIzipWithIndex():

assertEquals(
  Seq.of("a", "b", "c").zipWithIndex().toList(),
  Arrays.asList(tuple("a", 0L), tuple("b", 1L), tuple("c", 2L))
);

6. Преобразование отмеченных исключений в непроверенные

Допустим, у нас есть метод, который принимает строку и может генерировать проверенное исключение:

public Integer methodThatThrowsChecked(String arg) throws Exception {
    return arg.length();
}

Затем мы хотим отобразить элементыStream, применяя этот метод к каждому элементу. Нет способа обработать это исключение выше, поэтому нам нужно обработать это исключение в методеmap():

List collect = Stream.of("a", "b", "c").map(elem -> {
    try {
        return methodThatThrowsChecked(elem);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}).collect(Collectors.toList());

assertEquals(
    collect,
    Arrays.asList(1, 1, 1)
);

Мы мало что можем сделать с этим исключением из-за дизайна функциональных интерфейсов в Java, поэтому в предложении catch мы конвертируем проверенное исключение в непроверенное.

К счастью, в jOOL есть классUnchecked, у которого есть методы, которые могут преобразовывать проверенные исключения в непроверенные исключения:

List collect = Stream.of("a", "b", "c")
  .map(Unchecked.function(elem -> methodThatThrowsChecked(elem)))
  .collect(Collectors.toList());

assertEquals(
  collect,
  Arrays.asList(1, 1, 1)
);

Мы превращаем вызовmethodThatThrowsChecked() в методUnchecked.function(), который обрабатывает преобразование исключений ниже.

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

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

Реализация всех этих примеров и фрагментов кода можно найти вGitHub project - это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.