Introduzione a jOOL

1. Panoramica

In questo articolo vedremo il jOOLlibrary - un altro prodotto di jOOQ.

2. Dipendenza da Maven

Iniziamo aggiungendo una dipendenza Maven al tuo pom.xml :

 org.jooq jool 0.9.12  

Puoi trovare l'ultima versione qui.

3. Interfacce funzionali

In Java 8, le interfacce funzionali sono piuttosto limitate. Accettano il numero massimo di due parametri e non hanno molte funzionalità aggiuntive.

jOOL risolve questo problema dimostrando una serie di nuove interfacce funzionali che possono accettare anche 16 parametri (da Function1 fino a Function16) e sono arricchite con ulteriori metodi pratici.

Ad esempio, per creare una funzione che accetta tre argomenti, possiamo usare Function3:

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

In Java puro, dovresti implementarlo da solo. Oltre a ciò, le interfacce funzionali di jOOL hanno un metodo applyPartially () che ci consente di eseguire facilmente un'applicazione parziale:

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

Quando abbiamo un metodo di tipo Function2 , possiamo trasformarlo facilmente in una BiFunction Java standard utilizzando un metodo toBiFunction () :

BiFunction biFunc = addTwoNumbers.toBiFunction();

Allo stesso modo, esiste un metodo toFunction () nel tipo Function1 .

4. Tuple

Una tupla è un costrutto molto importante in un mondo di programmazione funzionale. È un contenitore tipizzato per i valori in cui ogni valore può avere un tipo diverso. Le tuple sono spesso usate come argomenti di funzione .

Sono anche molto utili quando si eseguono trasformazioni su un flusso di eventi. In jOOL, abbiamo tuple che possono avvolgere da uno a sedici valori, forniti da Tuple1 fino ai tipi Tuple16 :

tuple(2, 2)

E per quattro valori:

tuple(1,2,3,4); 

Consideriamo un esempio quando abbiamo una sequenza di tuple che portava 3 valori:

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

Possiamo usare diversi tipi di trasformazioni sulle tuple. Innanzitutto, chiamiamo un metodo limit2 () per prendere solo due valori da Tuple3. Quindi, stiamo chiamando un metodo concat () per concatenare due tuple.

Nel risultato, otteniamo valori di tipo Tuple4 .

5. Seq

Il costrutto Seq aggiunge metodi di livello superiore a un flusso mentre spesso utilizza i metodi sottostanti.

5.1. Contiene operazioni

Possiamo trovare un paio di varianti di metodi che verificano la presenza di elementi in una Seq. Alcuni di questi metodi utilizzano un metodo anyMatch () da una classe 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. Partecipa alle operazioni

Quando abbiamo due flussi e vogliamo unirli (simile a un'operazione di join SQL di due set di dati), l'utilizzo di una classe Stream standard non è un modo molto elegante per farlo:

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

Dobbiamo raccogliere il flusso corretto in un elenco, per evitare che java.lang.IllegalStateException: il flusso sia già stato utilizzato o chiuso. Successivamente, è necessario eseguire un'operazione con effetti collaterali accedendo a un elenco rightCollected da un metodo di filtro . È un modo soggetto a errori e non elegante per unire due set di dati.

Fortunatamente, Seq ha metodi utili per eseguire join interni, sinistri e destri sui set di dati. Questi metodi nascondono un'implementazione di esso esponendo eleganti API.

Possiamo fare un inner join usando un metodo 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)) );

Possiamo fare giunzioni destra e sinistra di conseguenza:

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

Esiste anche un metodo crossJoin () che rende possibile creare un join cartesiano di due set di dati:

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

Stiamo avvolgendo una chiamata a un methodThatThrowsChecked () in un metodo Unchecked.function () che gestisce la conversione delle eccezioni sottostanti.

7. Conclusione

Questo articolo mostra come utilizzare la libreria jOOL che aggiunge utili metodi aggiuntivi all'API Stream standard di Java .

L'implementazione di tutti questi esempi e frammenti di codice può essere trovata nel progetto GitHub: questo è un progetto Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.