Въведение в jOOL

1. Общ преглед

В тази статия ще разгледаме jOOLбиблиотека - друг продукт от jOOQ.

2. Зависимост на Maven

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

 org.jooq jool 0.9.12  

Можете да намерите най-новата версия тук.

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

В Java 8 функционалните интерфейси са доста ограничени. Те приемат максималния брой от два параметъра и нямат много допълнителни функции.

jOOL определя, че чрез доказване набор от нови функционални интерфейси, които могат да приемат дори 16 параметри (от Function1 до Function16) и са обогатени с допълнителни удобни методи.

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

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

В чиста Java ще трябва да го приложите сами. Освен това, функционалните интерфейси от jOOL имат метод applyPartial (), който ни позволява да изпълняваме частично приложение лесно:

Function2 addTwoNumbers = (v1, v2) -> v1 + v2; Function1 addToTwo = addTwoNumbers.applyPartially(2); Integer result = addToTwo.apply(5); assertEquals(result, (Integer) 7);

Когато имаме метод от тип Function2 , можем лесно да го трансформираме в стандартен Java BiFunction, като използваме метода toBiFunction () :

BiFunction biFunc = addTwoNumbers.toBiFunction();

По подобен начин има метод toFunction () във тип Function1 .

4. Кортежи

Кортежът е много важна конструкция в един функционален програмен свят. Това е типизиран контейнер за стойности, при които всяка стойност може да има различен тип. Кортежи често се използват като аргументи на функцията .

Те също са много полезни, когато извършват трансформации в поток от събития. В 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 добавя методи на по-високо ниво в поток, докато често използва своите методи отдолу.

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

Трябва да съберем десния поток в списък, за да предотвратим java.lang.IllegalStateException: потокът вече е бил опериран или затворен. След това трябва да извършим операция за страничен ефект чрез достъп до списък rightCollected от метод на филтър . Склонен е към грешки и не е елегантен начин да се присъединят два набора от данни.

За щастие, Seq има полезни методи за вътрешно, ляво и дясно обединяване на набори от данни. Тези методи скриват изпълнението му, излагайки елегантен 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. Manipulating a Seq

Seq has many useful methods for manipulating sequences of elements. Let's look at some of them.

We can use a cycle() method to take repeatedly elements from a source sequence. It will create an infinite stream, so we need to be careful when collecting results to a list thus we need to use a limit() method to transform infinite sequence into finite one:

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

Let's say that we want to duplicate all elements from one sequence to the second sequence. The duplicate() method does exactly that:

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

Returning type of a duplicate() method is a tuple of two sequences.

Let's say that we have a sequence of integers and we want to split that sequence into two sequences using some predicate. We can use a partition() method:

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. Grouping Elements

Grouping elements by a key using the Stream API is cumbersome and non-intuitive – because we need to use collect() method with a Collectors.groupingBy collector.

Seq hides that code behind a groupBy() method that returns Map so there is no need to use a collect() method explicitly:

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. Skipping Elements

Let's say that we have a sequence of elements and we want to skip elements while a predicate is not matched. When a predicate is satisfied, elements should land in a resulting sequence.

We can use a skipWhile() method for that:

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

We can achieve the same result using a skipUntil() method:

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

5.6. Zipping Sequences

When we're processing sequences of elements, often there is a need to zip them into one sequence.

The zip() API that could be used to zip two sequences into one:

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

The resulting sequence contains tuples of two elements.

When we are zipping two sequences, but we want to zip them in a specific way we can pass a BiFunction to a zip() method that defines the way of zipping elements:

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

Sometimes, it is useful to zip sequence with an index of elements in this sequence, via the zipWithIndex() API:

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

6. Converting Checked Exceptions to Unchecked

Let's say that we have a method that takes a string and can throw a checked exception:

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

Then we want to map elements of a Stream applying that method to each element. There is no way to handle that exception higher so we need to handle that exception in a map() method:

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

There is not much we can do with that exception because of the design of functional interfaces in Java so in a catch clause, we are converting a checked exception into unchecked one.

Fortunately, in a jOOL there is an Unchecked class that has methods that can convert checked exceptions into unchecked exceptions:

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 Stream API.

Изпълнението на всички тези примери и кодови фрагменти може да се намери в проекта GitHub - това е проект на Maven, така че трябва да е лесно да се импортира и да се изпълнява както е.