1. Обзор
В этой статье мы рассмотрим библиотеку jOOL | – еще один продукт от jOOQ .
2. Зависимость Maven
Давайте начнем с добавления зависимости Maven к вашему pom.xml :
org.jooq jool 0.9.12
Вы можете найти последнюю версию здесь .
3. Функциональные Интерфейсы
В Java 8 функциональные интерфейсы довольно ограничены. Они принимают максимальное количество двух параметров и не имеют большого количества дополнительных функций.
joomla исправляет это, доказывая набор новых функциональных интерфейсов, которые могут принимать даже 16 параметров (из Function 1 до Function16 ) и обогащаются дополнительными удобными методами.
Например, чтобы создать функцию, которая принимает три аргумента, мы можем использовать Function 3:
Function3lengthSum = (v1, v2, v3) -> v1.length() + v2.length() + v3.length();
В чистой Java вам нужно было бы реализовать его самостоятельно. Кроме того, функциональные интерфейсы от joomla имеют метод apply Particularly () , который позволяет нам легко выполнять частичное приложение:
Function2addTwoNumbers = (v1, v2) -> v1 + v2; Function1 addToTwo = addTwoNumbers.applyPartially(2); Integer result = addToTwo.apply(5); assertEquals(result, (Integer) 7);
Когда у нас есть метод типа Function 2 , мы можем легко преобразовать его в стандартную Java BiFunction с помощью метода toBiFunction() :
BiFunction biFunc = addTwoNumbers.toBiFunction();
Аналогично, существует метод function() в типе Function1 .
4. Кортежи
Кортеж-очень важная конструкция в мире функционального программирования. Это типизированный контейнер для значений, где каждое значение может иметь свой тип. Кортежи часто используются в качестве аргументов функции .
Они также очень полезны при выполнении преобразований в потоке событий. В joomla у нас есть кортежи, которые могут переносить от одного до шестнадцати значений, предоставляемых типами 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")) );
Мы можем использовать различные виды преобразований в кортежах. Во-первых, мы вызываем метод limit 2 () , чтобы взять только два значения из Tuple3. Затем мы вызываем метод concat() для объединения двух кортежей.
В результате мы получаем значения типа Tuple 4 .
5. Seq
Конструкция Seq добавляет методы более высокого уровня в Stream , в то время как часто использует свои методы внизу.
5.1. Содержит Операции
Мы можем найти несколько вариантов методов проверки наличия элементов в a Seq. Некоторые из этих методов используют метод any Match() из класса 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 join двух наборов данных), использование стандартного класса Stream – не очень элегантный способ сделать это:
Streamleft = 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 stream в список, чтобы предотвратить java.lang.IllegalStateException: поток уже был обработан или закрыт. Далее нам нужно сделать операцию побочного эффекта, обратившись к списку right Collected из метода filter . Это подверженный ошибкам и не очень элегантный способ объединения двух наборов данных.
К счастью, Seq имеет полезные методы для выполнения внутренних, левых и правых соединений в наборах данных. Эти методы скрывают реализацию it, выставляя элегантный API.
Мы можем сделать внутреннее соединение с помощью метода inner Join() :
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)) );
Существует даже метод cross Join () , который позволяет сделать декартово соединение двух наборов данных:
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. Манипулирование последовательностью
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. Группировка Элементов
Группировка элементов по ключу с помощью API Stream громоздка и неинтуитивна-потому что нам нужно использовать метод collect() с Collectors.groupingBy collector.
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) );
Мы можем достичь того же результата, используя метод skip Until() :
assertEquals( Seq.of(1, 2, 3, 4, 5).skipUntil(i -> i == 3).toList(), Arrays.asList(3, 4, 5) );
5.6. Последовательности Молнии
Когда мы обрабатываем последовательности элементов, часто возникает необходимость сжать их в одну последовательность.
API zip () , который можно использовать для объединения двух последовательностей в одну:
assertEquals( Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c")).toList(), Arrays.asList(tuple(1, "a"), tuple(2, "b"), tuple(3, "c")) );
Полученная последовательность содержит кортежи из двух элементов.
Когда мы застегиваем две последовательности, но хотим застегнуть их определенным образом, мы можем передать бифункцию методу 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") );
Иногда полезно zip-последовательность с индексом элементов в этой последовательности с помощью API zipWithIndex() :
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() :
Listcollect = 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 мы преобразуем проверенное исключение в непроверенное.
К счастью, в джуле есть Непроверенный класс, имеющий методы, которые могут преобразовывать проверенные исключения в непроверенные исключения:
Listcollect = Stream.of("a", "b", "c") .map(Unchecked.function(elem -> methodThatThrowsChecked(elem))) .collect(Collectors.toList()); assertEquals( collect, Arrays.asList(1, 1, 1) );
Мы оборачиваем вызов метода , который бросает Checked() в Unchecked.function() метод, который обрабатывает преобразование исключений внизу.
7. Заключение
В этой статье показано, как использовать библиотеку joomla, которая добавляет полезные дополнительные методы в стандарт Java Stream API.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub – это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.