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
T
Você 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