Introducción a jOOL

1. Información general

En este artículo, veremos el jOOLbiblioteca - otro producto de jOOQ.

2. Dependencia de Maven

Comencemos agregando una dependencia de Maven a su pom.xml :

 org.jooq jool 0.9.12  

Puede encontrar la última versión aquí.

3. Interfaces funcionales

En Java 8, las interfaces funcionales son bastante limitadas. Aceptan el número máximo de dos parámetros y no tienen muchas características adicionales.

jOOL corrige eso probando un conjunto de nuevas interfaces funcionales que pueden aceptar incluso 16 parámetros (desde Function1 hasta Function16) y están enriquecidas con métodos prácticos adicionales.

Por ejemplo, para crear una función que tome tres argumentos, podemos usar Function3:

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

En Java puro, necesitaría implementarlo usted mismo. Además de eso, las interfaces funcionales de jOOL tienen un método applyPartially () que nos permite realizar una aplicación parcial fácilmente:

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

Cuando tenemos un método que es de tipo Function2 , podemos transformarlo fácilmente en una BiFunction Java estándar usando un método toBiFunction () :

BiFunction biFunc = addTwoNumbers.toBiFunction();

De manera similar, hay un método toFunction () en el tipo Function1 .

4. Tuplas

Una tupla es una construcción muy importante en un mundo de programación funcional. Es un contenedor escrito para valores donde cada valor puede tener un tipo diferente. Las tuplas se utilizan a menudo como argumentos de función .

También son muy útiles cuando se realizan transformaciones en una secuencia de eventos. En jOOL, tenemos tuplas que pueden ajustar desde uno hasta dieciséis valores, proporcionados por los tipos Tuple1 hasta Tuple16 :

tuple(2, 2)

Y para cuatro valores:

tuple(1,2,3,4); 

Consideremos un ejemplo cuando tenemos una secuencia de tuplas que llevan 3 valores:

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

Podemos usar diferentes tipos de transformaciones en tuplas. Primero, llamamos a un método limit2 () para tomar solo dos valores de Tuple3. Luego, llamamos a un método concat () para concatenar dos tuplas.

En el resultado, obtenemos valores que son de tipo Tuple4 .

5. Seq

La construcción Seq agrega métodos de nivel superior en una secuencia, mientras que a menudo usa sus métodos debajo.

5.1. Contiene operaciones

Podemos encontrar un par de variantes de métodos que verifican la presencia de elementos en una Seq. Algunos de esos métodos usan un método anyMatch () de una clase 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. Unir operaciones

Cuando tenemos dos flujos y queremos unirlos (similar a una operación de unión SQL de dos conjuntos de datos), usar una clase Stream estándar no es una forma muy elegante de hacer esto:

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

Necesitamos recopilar el flujo correcto en una lista para evitar java.lang.IllegalStateException: el flujo ya ha sido operado o cerrado. A continuación, necesitamos realizar una operación de efectos secundarios accediendo a una lista rightCollected desde un método de filtro . Es una forma propensa a errores y no elegante de unir dos conjuntos de datos.

Afortunadamente, Seq tiene métodos útiles para hacer uniones internas, izquierdas y derechas en conjuntos de datos. Esos métodos ocultan una implementación que expone una elegante API.

Podemos hacer una unión interna usando un método 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)) );

Podemos hacer uniones a derecha e izquierda en consecuencia:

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

Incluso hay un método crossJoin () que hace posible realizar una unión cartesiana de dos conjuntos de datos:

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

Estamos terminando una llamada a un methodThatThrowsChecked () en un Unchecked.function () método que controla la conversión de excepciones debajo.

7. Conclusión

Este artículo muestra cómo usar la biblioteca jOOL que agrega métodos adicionales útiles a la API Stream estándar de Java .

La implementación de todos estos ejemplos y fragmentos de código se puede encontrar en el proyecto GitHub; este es un proyecto de Maven, por lo que debería ser fácil de importar y ejecutar tal como está.