O VAVR (anteriormente conhecido como Javaslang) é uma biblioteca funcional sem fins lucrativos para Java 8+. Ele permite que você escreva códigos funcionais do tipo Scala em Java e serve para reduzir a quantidade de códigos e melhorar sua qualidade.
Site da biblioteca .
Abaixo do corte, há uma tradução de um artigo que sistematiza informações na
API do Vavr Collections .
Traduzido por
@middle_javaArtigo original da última modificação: 15 de agosto de 2019
1. Visão geral
A biblioteca Vavr, anteriormente conhecida como Javaslang, é uma biblioteca funcional para Java. Neste artigo, exploramos sua poderosa API de coleta.
Consulte
este artigo para obter mais informações sobre esta biblioteca.
2. Coleções persistentes
Uma coleção persistente, quando modificada, cria uma nova versão da coleção sem alterar a versão atual.
O suporte para várias versões da mesma coleção pode levar ao uso ineficiente da CPU e da memória. No entanto, a biblioteca de coleções do Vavr supera isso compartilhando a estrutura de dados entre diferentes versões da coleção.
Isso é fundamentalmente diferente de
unmodifiableCollection() da classe de utilitário Java
Collections , que simplesmente fornece um wrapper para a coleção base.
Tentar modificar essa coleção
UnsupportedOperationException vez de criar uma nova versão. Além disso, a coleção base ainda é mutável através de um link direto para ela.
3. Tranversável
Traversable é o tipo base de todas as coleções do Vavr. Essa interface define métodos comuns a todas as estruturas de dados.
Ele fornece alguns métodos padrão úteis, como
size() ,
get() ,
filter() ,
isEmpty() e outros que são herdados pelas sub-interfaces.
Exploramos ainda mais a biblioteca de coleções.
4. Seq
Vamos começar com as sequências.
A interface
Seq é uma estrutura de dados seqüencial. Essa é a interface pai para
List ,
Stream ,
Queue ,
Array ,
Vector e
CharSeq . Todas essas estruturas de dados têm suas próprias propriedades únicas, as quais discutiremos abaixo.
4.1 Lista
List é uma operação calculada energeticamente (avaliada com avidez, é executada assim que os valores de seus operandos se tornam conhecidos) uma sequência de elementos que estendem a interface
LinearSeq .
As
List persistentes
List construídas recursivamente usando a
cabeça e a
cauda :
- A cabeça é o primeiro elemento
- Cauda - uma lista contendo os elementos restantes (esta lista também é formada a partir da cabeça e da cauda)
A API da
List contém métodos estáticos de fábrica que você pode usar para criar uma
List . Você pode usar o método static
of() para criar uma instância da
List partir de um ou mais objetos.
Você também pode usar o método static
empty() para criar uma
List vazia e o método
ofAll() para criar uma
List do tipo
Iterable :
List < String > list = List.of( "Java", "PHP", "Jquery", "JavaScript", "JShell", "JAVA");
Vejamos alguns exemplos de manipulação de lista.
Podemos usar o método
drop() e suas variantes para remover os primeiros
N elementos:
List list1 = list.drop(2); assertFalse(list1.contains("Java") && list1.contains("PHP")); List list2 = list.dropRight(2); assertFalse(list2.contains("JAVA") && list2.contains("JShell")); List list3 = list.dropUntil(s - > s.contains("Shell")); assertEquals(list3.size(), 2); List list4 = list.dropWhile(s - > s.length() > 0); assertTrue(list4.isEmpty());
drop(int n) remove
n itens da lista, iniciando no primeiro item, enquanto
dropRight() faz o mesmo, iniciando no último item da lista.
dropUntil() remove os itens da lista até que o predicado seja
true , enquanto
dropWhile() remove os itens até que o predicado seja
true .
Existem também os
dropRightWhile() e
dropRightUntil() que removem itens começando pela direita.
Em seguida,
take(int n) usado para recuperar itens da lista. Ele pega
n itens da lista e depois para. Há também
takeRight(int n) , que recebe elementos do final da lista:
List list5 = list.take(1); assertEquals(list5.single(), "Java"); List list6 = list.takeRight(1); assertEquals(list5.single(), "Java"); List list7 = list.takeUntil(s - > s.length() > 6); assertEquals(list3.size(), 3);
Por fim,
takeUntil() pega elementos da lista até que o predicado se torne
true . Há uma variante de
takeWhile() que também aceita um argumento predicado.
Além disso, a API possui outros métodos úteis, por exemplo, mesmo
distinct() , que retorna uma lista de elementos com duplicatas excluídas, além de
distinctBy() , que aceita o
Comparator para determinar a igualdade.
É muito interessante que também exista
intersperse() , que insere um elemento entre cada elemento da lista. Isso pode ser muito conveniente para operações com
String :
List list8 = list .distinctBy((s1, s2) - > s1.startsWith(s2.charAt(0) + "") ? 0 : 1); assertEquals(list3.size(), 2); String words = List.of("Boys", "Girls") .intersperse("and") .reduce((s1, s2) - > s1.concat(" " + s2)) .trim(); assertEquals(words, "Boys and Girls");
Deseja dividir a lista em categorias? E para isso existe uma API:
Iterator < List < String >> iterator = list.grouped(2); assertEquals(iterator.head().size(), 2); Map < Boolean, List < String >> map = list.groupBy(e - > e.startsWith("J")); assertEquals(map.size(), 2); assertEquals(map.get(false).get().size(), 1); assertEquals(map.get(true).get().size(), 5);
O método
group(int n) divide
List em grupos de
n elementos cada. O método
groupdBy() pega uma
Function que contém a lógica para dividir a lista e retorna um
Map com dois elementos:
true e
false .
A chave
true é mapeada para a
List elementos que satisfazem a condição especificada em
Function . A chave
false mapeada para a
List elementos que não atendem a essa condição.
Como esperado, ao alterar a
List , a
List original não é alterada. Em vez disso, a nova versão da
List sempre retornada.
Também podemos interagir com o
List usando a semântica da pilha - extraindo elementos de acordo com o princípio “último a entrar, primeiro a sair” (LIFO). Nesse sentido, existem métodos de API como
peek() ,
pop() e
push() para manipular a pilha:
List < Integer > intList = List.empty(); List < Integer > intList1 = intList.pushAll(List.rangeClosed(5, 10)); assertEquals(intList1.peek(), Integer.valueOf(10)); List intList2 = intList1.pop(); assertEquals(intList2.size(), (intList1.size() - 1));
A função
pushAll() é usada para inserir um intervalo de números inteiros na pilha, e a função
peek() é usada para recuperar o elemento principal da pilha. Há também um método
peekOption() que pode
peekOption() o resultado em um objeto
Option .
Existem outros métodos interessantes e realmente úteis na interface da
List que são completamente documentados nos documentos
Java .
4.2 Fila
A
Queue imutável armazena elementos, permitindo recuperá-los de acordo com o princípio FIFO (primeiro a entrar, primeiro a sair).
Queue interna consiste em duas listas vinculadas: a
List frontal e a
List traseira. A
List frontal contém itens removidos da fila e a
List traseira contém os itens na fila.
Isso permite que você coloque as operações de enfileiramento e remoção da fila na complexidade
O (1) . Quando a
List termina na
List frontal quando é removida da fila, a
List posterior
List revertida e se torna a nova
List frontal.
Vamos criar uma fila:
Queue < Integer > queue = Queue.of(1, 2); Queue < Integer > secondQueue = queue.enqueueAll(List.of(4, 5)); assertEquals(3, queue.size()); assertEquals(5, secondQueue.size()); Tuple2 < Integer, Queue < Integer >> result = secondQueue.dequeue(); assertEquals(Integer.valueOf(1), result._1); Queue < Integer > tailQueue = result._2; assertFalse(tailQueue.contains(secondQueue.get(0)));
A função
dequeue() remove o elemento head da
Queue e retorna
Tuple2<T, Q> . O primeiro elemento da tupla é o elemento principal removido da fila, o segundo elemento da tupla são os demais elementos da
Queue .
Podemos usar a
combination(n) para obter todas as
N combinações possíveis de elementos na
Queue :
Queue < Queue < Integer >> queue1 = queue.combinations(2); assertEquals(queue1.get(2).toCharSeq(), CharSeq.of("23"));
Mais uma vez, a
Queue original não muda ao adicionar / remover itens da fila.
4.3 Stream
Stream é uma implementação de uma lista vinculada lenta que é significativamente diferente de
java.util.stream . Ao contrário do
java.util.stream , o
Stream Vavr armazena dados e calcula preguiçosamente os elementos subseqüentes.
Digamos que temos números inteiros de
Stream :
Stream < Integer > s = Stream.of(2, 1, 3, 4);
Ao imprimir o resultado de
s.toString() no console, apenas
Stream (2 ,?) será exibido. Isso significa que apenas o elemento principal
Stream foi calculado, enquanto os elementos finais não foram.
Chamar
s.get(3) e exibir o resultado de
s.tail() retornará
Stream (1, 3, 4 ,?) . Pelo contrário, se você não chamar
s.get(3) - o que fará o
Stream calcular o último elemento - somente o
Stream (1 ,?) será o resultado de
s.tail() ) . Isso significa que apenas o primeiro elemento da cauda foi calculado.
Esse comportamento pode melhorar o desempenho e permite que o
Stream seja usado para representar seqüências que são (teoricamente) infinitamente longas.
Stream no Vavr é imutável e pode ser
Empty ou
Cons .
Cons consiste no elemento principal e na cauda calculada preguiçosamente do
Stream . Diferentemente da
List , o
Stream armazena
Stream o elemento head na memória. Os elementos de cauda são calculados conforme necessário.
Vamos criar um
Stream de 10 números inteiros positivos e calcular a soma dos números pares:
Stream < Integer > intStream = Stream.iterate(0, i - > i + 1) .take(10); assertEquals(10, intStream.size()); long evenSum = intStream.filter(i - > i % 2 == 0) .sum() .longValue(); assertEquals(20, evenSum);
Diferente da API de
Stream do Java 8, o
Stream in Vavr é uma estrutura de dados para armazenar uma sequência de elementos.
Portanto, ele possui métodos como
get() ,
append() ,
insert() e outros para manipular seus elementos.
drop() ,
distinct() e alguns outros métodos discutidos anteriormente também estão disponíveis.
Por fim, vamos demonstrar rapidamente
tabulate() no
Stream . Este método retorna um
Stream comprimento
n contendo elementos resultantes da aplicação da função:
Stream < Integer > s1 = Stream.tabulate(5, (i) - > i + 1); assertEquals(s1.get(2).intValue(), 3);
Também podemos usar
zip() para criar um
Stream partir de
Tuple2<Integer, Integer> , que contém elementos formados pela combinação de dois
Stream :
Stream < Integer > s = Stream.of(2, 1, 3, 4); Stream < Tuple2 < Integer, Integer >> s2 = s.zip(List.of(7, 8, 9)); Tuple2 < Integer, Integer > t1 = s2.get(0); assertEquals(t1._1().intValue(), 2); assertEquals(t1._2().intValue(), 7);
4.4 Matriz
Array é uma sequência indexada imutável que fornece acesso aleatório eficiente. É baseado em uma
matriz de objetos Java. Em essência, este é um wrapper
Traversable para uma matriz de objetos do tipo
TVocê pode criar uma instância de
Array usando o método estático
of() . Além disso, você pode criar um intervalo de elementos usando os métodos estáticos
range() e
rangeBy() . O método
rangeBy() possui um terceiro parâmetro, que permite determinar a etapa.
Os métodos
range() e
rangeBy() criarão elementos, começando apenas do valor inicial para o valor final menos um. Se precisarmos incluir o valor final, podemos usar
rangeClosed() ou
rangeClosedBy() :
Array < Integer > rArray = Array.range(1, 5); assertFalse(rArray.contains(5)); Array < Integer > rArray2 = Array.rangeClosed(1, 5); assertTrue(rArray2.contains(5)); Array < Integer > rArray3 = Array.rangeClosedBy(1, 6, 2); assertEquals(list3.size(), 3);
Vamos trabalhar com os elementos usando o índice:
Array < Integer > intArray = Array.of(1, 2, 3); Array < Integer > newArray = intArray.removeAt(1); assertEquals(3, intArray.size()); assertEquals(2, newArray.size()); assertEquals(3, newArray.get(1).intValue()); Array < Integer > array2 = intArray.replace(1, 5); assertEquals(s1.get(0).intValue(), 5);
4.5 Vetor
Vector é um cruzamento entre
Array e
List , fornecendo outra sequência indexada de elementos, permitindo acesso aleatório e modificação em tempo constante:
Vector < Integer > intVector = Vector.range(1, 5); Vector < Integer > newVector = intVector.replace(2, 6); assertEquals(4, intVector.size()); assertEquals(4, newVector.size()); assertEquals(2, intVector.get(1).intValue()); assertEquals(6, newVector.get(1).intValue());
4.6 Charseq
CharSeq é um objeto de coleção para representar uma sequência de caracteres primitivos. Em essência, este é um wrapper para
String com a adição de operações de coleção.
Para criar o
CharSeq você deve fazer o seguinte.
CharSeq chars = CharSeq.of("vavr"); CharSeq newChars = chars.replace('v', 'V'); assertEquals(4, chars.size()); assertEquals(4, newChars.size()); assertEquals('v', chars.charAt(0)); assertEquals('V', newChars.charAt(0)); assertEquals("Vavr", newChars.mkString());
5. Defina
Esta seção discute as várias implementações do
Set na biblioteca de coleções. Um recurso exclusivo da estrutura de dados do
Set é que ele não permite valores duplicados.
Existem várias implementações do
Set . O principal é o
HashSet .
TreeSet não permite elementos duplicados e pode ser classificado.
LinkedHashSet preserva a ordem de inserção dos elementos.
Vamos dar uma olhada mais de perto nessas implementações, uma após a outra.
5.1 Hashset
HashSet possui métodos estáticos de fábrica para criar novas instâncias. Alguns dos quais estudamos anteriormente neste artigo, por exemplo,
of() ,
ofAll() e variações nos métodos
range() .
A diferença entre os dois
conjuntos pode ser obtida usando o método
diff() . Além disso, os métodos
union() e
intersect() retornam a união e a interseção de dois
conjuntos :
HashSet < Integer > set0 = HashSet.rangeClosed(1, 5); HashSet < Integer > set0 = HashSet.rangeClosed(1, 5); assertEquals(set0.union(set1), HashSet.rangeClosed(1, 6)); assertEquals(set0.diff(set1), HashSet.rangeClosed(1, 2)); assertEquals(set0.intersect(set1), HashSet.rangeClosed(3, 5));
Também podemos executar operações básicas, como adicionar e remover elementos:
HashSet < String > set = HashSet.of("Red", "Green", "Blue"); HashSet < String > newSet = set.add("Yellow"); assertEquals(3, set.size()); assertEquals(4, newSet.size()); assertTrue(newSet.contains("Yellow"));
A implementação do
HashSet é baseada no
HAMT (Hash Array Arrie) , que apresenta desempenho superior ao
HashTable usual e sua estrutura o torna adequado para suportar coleções persistentes.
5.2 Treeset
TreeSet é uma implementação da interface
SortedSet . Ele armazena um conjunto de elementos classificados e é implementado usando árvores de pesquisa binária. Todas as suas operações são executadas durante o tempo
O (log n) .
Por padrão, os elementos
TreeSet são classificados em sua ordem natural.
Vamos criar um
SortedSet usando uma ordem de classificação natural:
SortedSet < String > set = TreeSet.of("Red", "Green", "Blue"); assertEquals("Blue", set.head()); SortedSet < Integer > intSet = TreeSet.of(1, 2, 3); assertEquals(2, intSet.average().get().intValue());
Para organizar os itens de maneira personalizada, passe uma instância do
Comparator ao criar o
TreeSet . Você também pode criar uma sequência a partir de um conjunto de elementos:
SortedSet < String > reversedSet = TreeSet.of(Comparator.reverseOrder(), "Green", "Red", "Blue"); assertEquals("Red", reversedSet.head()); String str = reversedSet.mkString(" and "); assertEquals("Red and Green and Blue", str);
5.3 Bitset
As coleções do Vavr também possuem uma implementação imutável do
BitSet . A interface
BitSet estende a interface
SortedSet .
BitSet pode ser criado usando métodos estáticos no
BitSet.Builder .
Como em outras implementações da estrutura de dados do
Set , o
BitSet não permite adicionar registros duplicados a um conjunto.
Ele herda métodos de manipulação da interface
Traversable . Observe que é diferente do
java.util.BitSet da biblioteca Java padrão.
BitSet dados do
BitSet não podem conter valores de
String .
Considere criar uma instância do
BitSet usando o método
of() factory:
BitSet < Integer > bitSet = BitSet.of(1, 2, 3, 4, 5, 6, 7, 8); BitSet < Integer > bitSet1 = bitSet.takeUntil(i - > i > 4); assertEquals(list3.size(), 4);
Para selecionar os quatro primeiros elementos
BitSet usamos o comando
takeUntil() . A operação retornou uma nova instância. Observe que o método
takeUntil() é definido na interface
Traversable , que é a interface pai do
BitSet .
Outros métodos e operações descritos acima definidos na interface
Traversable também se aplicam ao
BitSet .
6. Mapa
Map é uma estrutura de dados de valor-chave.
Map no Vavr é imutável e possui implementações para
HashMap ,
LinkedHashMap e
LinkedHashMap .
Normalmente, contratos de
mapas não permitem chaves duplicadas, enquanto valores duplicados mapeados para chaves diferentes podem ser.
6.1 Hashmap
HashMap é uma implementação da interface imutável do
Map . Ele armazena pares de valores-chave usando um hash de chaves.
Map no Vavr usa
Tuple2 para representar pares de valores-chave em vez do tipo de
Entry tradicional:
Map < Integer, List < Integer >> map = List.rangeClosed(0, 10) .groupBy(i - > i % 2); assertEquals(2, map.size()); assertEquals(6, map.get(0).get().size()); assertEquals(5, map.get(1).get().size());
Como o
HashSet , a implementação do
HashMap baseada no
HAMT (Hash Array Array) , que leva a um tempo constante para quase todas as operações.
Os elementos do
mapa podem ser filtrados por chave usando o método
filterKeys() ou por valor usando o método
filterValues() . Ambos os métodos consideram o
Predicate como argumento:
Map < String, String > map1 = HashMap.of("key1", "val1", "key2", "val2", "key3", "val3"); Map < String, String > fMap = map1.filterKeys(k - > k.contains("1") || k.contains("2")); assertFalse(fMap.containsKey("key3")); Map < String, String > map1 = map1.filterValues(v - > v.contains("3")); assertEquals(list3.size(), 1); assertTrue(fMap2.containsValue("val3"));
Você também pode transformar elementos do
map() usando o método
map() . Por exemplo, vamos converter
map1 em
Map<String, Integer> :
Map < String, Integer > map2 = map1.map( (k, v) - > Tuple.of(k, Integer.valueOf(v.charAt(v.length() - 1) + ""))); assertEquals(map2.get("key1").get().intValue(), 1);
6.2 Treemap
O
SortedMap é uma implementação da interface
SortedMap . Como no
TreeSet , uma instância personalizada do
Comparator usada para personalizar a classificação dos elementos do
TreeMap .
SortedMap demonstrar a criação do
SortedMap :
SortedMap < Integer, String > map = TreeMap.of(3, "Three", 2, "Two", 4, "Four", 1, "One"); assertEquals(1, map.keySet().toJavaArray()[0]); assertEquals("Four", map.get(4).get());
Por padrão, as entradas do
TreeMap são classificadas em ordem de chave natural. No entanto, você pode especificar o
Comparator a ser usado para classificação:
TreeMap < Integer, String > treeMap2 = TreeMap.of(Comparator.reverseOrder(), 3, "three", 6, "six", 1, "one"); assertEquals(treeMap2.keySet().mkString(), "631");
Como no caso do
TreeSet , a implementação do
TreeSet também é criada usando a árvore, portanto, suas operações têm tempo
O (log n) . O método
map.get(key) retorna
Option , que contém o valor da chave de
mapa especificada.
7. compatibilidade com Java
A API Vavr Collection é totalmente compatível com o Java Collection Framework. Vamos ver como isso é feito na prática.
7.1 Converter de Java para Vavr
Cada implementação de coleção no Vavr possui um método
ofAll() fábrica estática que aceita
java.util.Iterable . Isso permite criar uma coleção
Vavr a partir de uma coleção
Java . Da mesma forma, outro método de fábrica
ofAll() aceita diretamente o Java
Stream .
Para converter uma
List Java em uma
List imutável:
java.util.List < Integer > javaList = java.util.Arrays.asList(1, 2, 3, 4); List < Integer > vavrList = List.ofAll(javaList); java.util.stream.Stream < Integer > javaStream = javaList.stream(); Set < Integer > vavrSet = HashSet.ofAll(javaStream);
Outra função útil é
collector() , que pode ser usada em conjunto com
Stream.collect() para obter a coleção Vavr:
List < Integer > vavrList = IntStream.range(1, 10) .boxed() .filter(i - > i % 2 == 0) .collect(List.collector()); assertEquals(4, vavrList.size()); assertEquals(2, vavrList.head().intValue());
7.2 Converter de Vavr para Java
A interface
Value possui muitos métodos para converter de um tipo
Vavr para um tipo
Java . Esses métodos têm o formato
toJavaXXX() .
Considere alguns exemplos:
Integer[] array = List.of(1, 2, 3) .toJavaArray(Integer.class); assertEquals(3, array.length); java.util.Map < String, Integer > map = List.of("1", "2", "3") .toJavaMap(i - > Tuple.of(i, Integer.valueOf(i))); assertEquals(2, map.get("2").intValue());
Também podemos usar o Java 8
Collectors para coletar itens das coleções do Vavr:
java.util.Set < Integer > javaSet = List.of(1, 2, 3) .collect(Collectors.toSet()); assertEquals(3, javaSet.size()); assertEquals(1, javaSet.toArray()[0]);
7.3 Exibições de coleções Java
Além disso, a biblioteca fornece as chamadas
visualizações de coleção que funcionam melhor quando convertidas em coleções Java. Os métodos de transformação na seção anterior repetem (repetem) todos os elementos para criar uma coleção Java.
As visualizações, por outro lado, implementam interfaces Java padrão e delegam chamadas de método à coleção base do Vavr.
No momento da redação deste artigo, apenas a visualização em
List é suportada. Cada coleção seqüencial possui dois métodos: um para criar uma representação imutável e outro para mutável.
Chamar métodos para alterar em uma exibição imutável
UnsupportedOperationException .
Vejamos um exemplo:
@Test(expected = UnsupportedOperationException.class) public void givenVavrList_whenViewConverted_thenException() { java.util.List < Integer > javaList = List.of(1, 2, 3) .asJava(); assertEquals(3, javaList.get(2).intValue()); javaList.add(4); }
Para criar uma visão imutável:
java.util.List < Integer > javaList = List.of(1, 2, 3) .asJavaMutable(); javaList.add(4); assertEquals(4, javaList.get(3).intValue());
8. Conclusões
Neste tutorial, aprendemos sobre as várias estruturas de dados funcionais fornecidas pela API Vavr Collections. Também existem métodos de API úteis e produtivos que podem ser encontrados no
documento Java e
no Guia do
Usuário do Vavr Collections.
Finalmente, é importante observar que a biblioteca também define
Try ,
Option ,
Either e
Future , que estendem a interface
Value e, como resultado, implementam a interface Java
Iterable . Isso significa que, em algumas situações, eles podem se comportar como coleções.
O código fonte completo de todos os exemplos deste artigo pode ser encontrado no
Github .
Materiais adicionais:
habr.com/en/post/421839www.baeldung.com/vavrTraduzido por
@middle_java