Introdução ao jOOL

Introdução ao jOOL

1. Visão geral

Neste artigo, examinaremos a bibliotecajOOL __ - outro produto dejOOQ.

2. Dependência do Maven

Vamos começar adicionando uma dependência Maven ao seupom.xml:


    org.jooq
    jool
    0.9.12

Você pode encontrar a versão mais recentehere.

3. Interfaces Funcionais

No Java 8, as interfaces funcionais são bastante limitadas. Eles aceitam o número máximo de dois parâmetros e não possuem muitos recursos adicionais.

O jOOL corrige isso provando um conjunto de novas interfaces funcionais que podem aceitar até 16 parâmetros (deFunction1 atéFunction16)e são enriquecidas com métodos úteis adicionais.

Por exemplo, para criar uma função que recebe três argumentos, podemos usarFunction3:

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

Em Java puro, você precisaria implementá-lo sozinho. Além disso, as interfaces funcionais do jOOL possuem um métodoapplyPartially() que nos permite realizar uma aplicação parcial com facilidade:

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

Integer result = addToTwo.apply(5);

assertEquals(result, (Integer) 7);

Quando temos um método do tipoFunction2, podemos transformá-lo facilmente em um JavaBiFunction padrão usando um métodotoBiFunction():

BiFunction biFunc = addTwoNumbers.toBiFunction();

Da mesma forma, existe um métodotoFunction() no tipoFunction1.

4. Tuplas

Uma tupla é uma construção muito importante em um mundo de programação funcional. É um contêiner digitado para valores, onde cada valor pode ter um tipo diferente. Tuples are often used as function arguments.

Eles também são muito úteis ao fazer transformações em um fluxo de eventos. Em jOOL, temos tuplas que podem envolver de um a dezesseis valores, fornecidos porTuple1 atéTuple16 tipos:

tuple(2, 2)

E por quatro valores:

tuple(1,2,3,4);

Vamos considerar um exemplo em que temos uma sequência de tuplas que carregava 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 transformações em tuplas. Primeiro, chamamos um métodolimit2() para obter apenas dois valores deTuple3.. Em seguida, estamos chamando um métodoconcat() para concatenar duas tuplas.

No resultado, obtemos valores que são do tipoTuple4.

5. Seq

A construçãoSeq adiciona métodos de nível superior em umStream, enquanto freqüentemente usa seus métodos abaixo.

5.1. Contém Operações

Podemos encontrar algumas variantes de métodos de verificação da presença de elementos em umSeq.. Alguns desses métodos usam um métodoanyMatch() de uma classeStream:

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. Operações de junção

Quando temos dois fluxos e queremos juntá-los (semelhante a uma operação de junção SQL de dois conjuntos de dados), usar uma classeStream padrão não é uma maneira muito elegante de fazer isso:

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

Precisamos coletarright stream para uma lista, para evitarjava.lang.IllegalStateException: stream has already been operated upon or closed. Em seguida, precisamos fazer uma operação de efeito colateral acessando uma listarightCollected de um métodofilter. É propenso a erros e não é uma maneira elegante de unir dois conjuntos de dados.

Felizmente,Seq has useful methods to do inner, left and right joins on data sets. Esses métodos ocultam uma implementação dele expondo uma API elegante.

Podemos fazer uma junção interna usando um métodoinnerJoin():

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 fazer junções direita e esquerda de acordo:

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

Existe até um métodocrossJoin() que torna possível fazer uma junção cartesiana de dois conjuntos de dados:

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. Manipulando umSeq

Seq tem muitos métodos úteis para manipular sequências de elementos. Vejamos alguns deles.

Podemos usar um métodocycle() para obter elementos repetidamente de uma sequência de origem. Ele criará um fluxo infinito, então precisamos ter cuidado ao coletar resultados para uma lista, portanto, precisamos usar um métodolimit() para transformar a sequência infinita em uma finita:

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

Digamos que queremos duplicar todos os elementos de uma sequência para a segunda sequência. O métododuplicate() faz exatamente isso:

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

O tipo de retorno de um métododuplicate() é uma tupla de duas sequências.

Digamos que temos uma sequência de inteiros e queremos dividir essa sequência em duas sequências usando algum predicado. Podemos usar um métodopartition():

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. Elementos de Agrupamento

Agrupar elementos por uma chave usando a APIStream é complicado e não intuitivo - porque precisamos usar o métodocollect() com um coletorCollectors.groupingBy.

Seq oculta esse código atrás de um métodogroupBy() que retornaMap, portanto, não há necessidade de usar um métodocollect() explicitamente:

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. Ignorando Elementos

Digamos que temos uma sequência de elementos e queremos pular elementos enquanto um predicado não é correspondido. Quando um predicado é satisfeito, os elementos devem parar na sequência resultante.

Podemos usar um métodoskipWhile() para isso:

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

Podemos obter o mesmo resultado usando um métodoskipUntil():

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

5.6. Seqüências de compressão

Quando processamos sequências de elementos, muitas vezes é necessário compactá-los em uma sequência.

A APIzip() que pode ser usada para compactar duas sequências em uma:

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

A sequência resultante contém tuplas de dois elementos.

Quando estamos compactando duas sequências, mas queremos compactá-las de uma maneira específica, podemos passar um métodoBiFunction para um métodozip() que define a maneira de compactar os elementos:

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

Às vezes, é útil zipar a sequência com um índice de elementos nesta sequência, por meio da APIzipWithIndex():

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

6. Convertendo exceções verificadas em não verificadas

Digamos que temos um método que recebe uma string e pode lançar uma exceção verificada:

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

Então queremos mapear os elementos de aStream aplicando esse método a cada elemento. Não há maneira de lidar com essa exceção superior, então precisamos lidar com essa exceção em um métodomap():

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

Não há muito o que fazer com essa exceção, devido ao design de interfaces funcionais em Java; portanto, em uma cláusula catch, estamos convertendo uma exceção verificada em uma não verificada.

Felizmente, em um jOOL, há uma classeUnchecked que possui métodos que podem converter exceções verificadas em exceções não verificadas:

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

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

Estamos agrupando uma chamada para ummethodThatThrowsChecked() em um métodoUnchecked.function() que lida com a conversão de exceções por baixo.

7. Conclusão

Este artigo mostra como usar a biblioteca jOOL que adiciona métodos adicionais úteis à APIStream do padrão Java.

A implementação de todos esses exemplos e trechos de código pode ser encontrada emGitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.