VAVR (anteriormente conocido como Javaslang) es una biblioteca funcional sin fines de lucro para Java 8+. Le permite escribir código funcional similar a Scala en Java y sirve para reducir la cantidad de código y mejorar su calidad. 
Sitio de la biblioteca .
Debajo del corte hay una traducción de un artículo que sistematiza información sobre 
la API de Vavr Collections .
Traducido por 
@middle_javaArtículo original modificado por última vez: 15 de agosto de 2019
1. Descripción general
La biblioteca Vavr, anteriormente conocida como Javaslang, es una biblioteca funcional para Java. En este artículo, exploramos su poderosa colección API.
Consulte 
este artículo para obtener más información sobre esta biblioteca.
2. Colecciones persistentes
Una colección persistente, cuando se modifica, crea una nueva versión de la colección sin cambiar la versión actual.
La compatibilidad con múltiples versiones de la misma colección puede conducir a un uso ineficiente de la CPU y la memoria. Sin embargo, la biblioteca de la colección Vavr supera esto al compartir la estructura de datos entre diferentes versiones de la colección.
Esto es fundamentalmente diferente de 
unmodifiableCollection() de las 
Collections clase de utilidad de Java, que simplemente proporciona un contenedor para la colección base.
Intentar modificar dicha colección 
UnsupportedOperationException lugar de crear una nueva versión. Además, la colección base sigue siendo mutable a través de un enlace directo a ella.
3. Transitable
Traversable es el tipo base de todas las colecciones Vavr. Esta interfaz define métodos comunes a todas las estructuras de datos.
Proporciona algunos métodos predeterminados útiles, como 
size() , 
get() , 
filter() , 
isEmpty() y otros que son heredados por las 
isEmpty() .
Exploramos más la colección de la biblioteca.
4. Seq
Comencemos con las secuencias.
La interfaz 
Seq es una estructura de datos secuencial. Esta es la interfaz principal para 
List , 
Stream , 
Queue , 
Array , 
Vector y 
CharSeq . Todas estas estructuras de datos tienen sus propias propiedades únicas, que discutiremos a continuación.
4.1. Lista
List es una operación calculada energéticamente (evaluada con entusiasmo, se realiza tan pronto como se conocen los valores de sus operandos) una secuencia de elementos que extiende la interfaz 
LinearSeq .
Las 
List persistentes 
List construyen recursivamente usando la 
cabeza y la 
cola :
- La cabeza es el primer elemento.
- Cola : una lista que contiene los elementos restantes (esta lista también se forma a partir de la cabeza y la cola)
La API de 
List contiene métodos de fábrica estáticos que puede usar para crear una 
List . Puede usar el método static 
of() para crear una instancia de 
List partir de uno o más objetos.
También puede usar el método estático 
empty() para crear una 
List vacía y el método 
ofAll() para crear una 
List de tipo 
Iterable :
 List < String > list = List.of( "Java", "PHP", "Jquery", "JavaScript", "JShell", "JAVA"); 
Veamos algunos ejemplos de manipulación de listas.
Podemos usar el método 
drop() y sus variantes para eliminar los primeros 
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) elimina 
n elementos de la lista, comenzando por el primer elemento, mientras que 
dropRight() hace lo mismo, comenzando por el último elemento de la lista.
dropUntil() elimina los elementos de la lista hasta que el predicado sea 
true , mientras que 
dropWhile() elimina los elementos hasta que el predicado sea 
true .
También hay 
dropRightWhile() y 
dropRightUntil() que eliminan elementos que comienzan desde la derecha.
A continuación, 
take(int n) usa para recuperar elementos de la lista. Toma 
n elementos de la lista y luego se detiene. También hay 
takeRight(int n) , que toma elementos del final de la 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); 
Finalmente, 
takeUntil() toma elementos de la lista hasta que el predicado se vuelva 
true . Hay una variante de 
takeWhile() que también toma un argumento predicado.
Además, la API tiene otros métodos útiles, por ejemplo, 
distinct() , que devuelve una lista de elementos con duplicados eliminados, y 
distinctBy() , que acepta 
Comparator para determinar la igualdad.
Es muy interesante que también haya 
intersperse() , que inserta un elemento entre cada elemento de la lista. Esto puede ser muy conveniente para operaciones con 
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"); 
¿Quieres dividir la lista en categorías? Y para esto hay una 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); 
El método 
group(int n) divide 
List en grupos de 
n elementos cada uno. El método 
groupdBy() acepta una 
Function que contiene la lógica de división de la lista y devuelve un 
Map con dos elementos: 
true y 
false .
La clave 
true se asigna a la 
List elementos que satisfacen la condición especificada en 
Function . La clave 
false asigna a la 
List elementos que no satisfacen esta condición.
Como se esperaba, al cambiar la 
List , la 
List original no cambia realmente. En cambio, la nueva versión de 
List siempre se devuelve.
También podemos interactuar con 
List usando la semántica de la pila, extrayendo elementos de acuerdo con el principio de "último en entrar, primero en salir" (LIFO). En este sentido, existen métodos API como 
peek() , 
pop() y 
push() para manipular la pila:
 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)); 
La función 
pushAll() se usa para insertar un rango de enteros en la pila, y la función 
peek() se usa para recuperar el elemento head de la pila. También hay un método 
peekOption() que puede ajustar el resultado en un objeto 
Option .
Hay otros métodos interesantes y realmente útiles en la interfaz de la 
List que están documentados a fondo en los documentos de 
Java .
4.2. Cola
La 
Queue inmutable almacena elementos, lo que le permite recuperarlos de acuerdo con el principio FIFO (primero en entrar, primero en salir).
Queue interior consta de dos listas vinculadas: la 
List frontal y la 
List posterior. La 
List frontal contiene elementos que se eliminan de la cola y la 
List posterior contiene los elementos en cola.
Esto le permite colocar las operaciones de cola y eliminación de la cola en la complejidad 
O (1) . Cuando la 
List finaliza en la 
List frontal cuando se elimina de la cola, la 
List posterior 
List invierte y se convierte en la nueva 
List frontal.
Vamos a crear una cola:
 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))); 
La función 
dequeue() elimina el elemento head de 
Queue y devuelve 
Tuple2<T, Q> . El primer elemento de la tupla es el elemento principal eliminado de la cola, el segundo elemento de la tupla son los elementos restantes de la 
Queue .
Podemos usar la 
combination(n) para obtener todas las 
N combinaciones posibles de elementos en la 
Queue :
 Queue < Queue < Integer >> queue1 = queue.combinations(2); assertEquals(queue1.get(2).toCharSeq(), CharSeq.of("23")); 
Una vez más, la 
Queue original no cambia al agregar / eliminar elementos de la cola.
4.3. Stream
Stream es una implementación de una lista perezosamente vinculada que es significativamente diferente de 
java.util.stream . A diferencia de 
java.util.stream , 
Stream Vavr almacena datos y calcula perezosamente elementos posteriores.
Digamos que tenemos enteros 
Stream :
 Stream < Integer > s = Stream.of(2, 1, 3, 4); 
Al imprimir el resultado de 
s.toString() en la consola, solo se mostrará 
Stream (2 ,?) . Esto significa que solo se calculó el elemento principal de 
Stream , mientras que los elementos de cola no.
Llamar a 
s.get(3) y luego mostrar el resultado de 
s.tail() devuelve 
Stream (1, 3, 4 ,?) . Por el contrario, si no llama 
s.get(3) a 
s.get(3) , lo que hará que 
Stream calcule el último elemento, solo 
Stream (1 ,?) será el resultado de 
s.tail() ) . Esto significa que solo se calculó el primer elemento de cola.
Este comportamiento puede mejorar el rendimiento y permite que 
Stream se use para representar secuencias que son (teóricamente) infinitamente largas.
Stream en Vavr es inmutable y puede estar 
Empty o en 
Cons . 
Cons consisten en el elemento principal y la cola calculada perezosamente de 
Stream . A diferencia de 
List , 
Stream almacena el elemento head en la memoria. Los elementos de cola se calculan según sea necesario.
Creemos un 
Stream de 10 enteros positivos y calculemos la suma de 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); 
A diferencia de la API 
Stream de Java 8, 
Stream in Vavr es una estructura de datos para almacenar una secuencia de elementos.
Por lo tanto, tiene métodos como 
get() , 
append() , 
insert() y otros para manipular sus elementos. 
drop() , 
distinct() y algunos otros métodos discutidos anteriormente también están disponibles.
Finalmente, demostremos rápidamente 
tabulate() en 
Stream . Este método devuelve un 
Stream longitud 
n contiene elementos que son el resultado de aplicar la función:
 Stream < Integer > s1 = Stream.tabulate(5, (i) - > i + 1); assertEquals(s1.get(2).intValue(), 3); 
También podemos usar 
zip() para crear un 
Stream desde 
Tuple2<Integer, Integer> , que contiene elementos formados al combinar dos 
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 es una secuencia indexada inmutable que proporciona un acceso aleatorio eficiente. Se basa en una 
matriz de objetos Java. En esencia, este es un contenedor 
Traversable para una matriz de objetos de tipo 
TPuede crear una instancia de 
Array utilizando el método estático 
of() . Además, puede crear un rango de elementos utilizando los métodos estáticos 
range() y 
rangeBy() . El método 
rangeBy() tiene un tercer parámetro, que le permite determinar el paso.
Los métodos 
range() y 
rangeBy() crearán elementos, comenzando solo desde el valor inicial hasta el valor final menos uno. Si necesitamos incluir el valor final, podemos usar 
rangeClosed() o 
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); 
Trabajemos con los elementos usando el í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. Vector
Vector es un cruce entre 
Array y 
List , que proporciona otra secuencia indexada de elementos, que permite el acceso aleatorio y la modificación en tiempo 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 es un objeto de colección para representar una secuencia de caracteres primitivos. En esencia, este es un contenedor para 
String con la adición de operaciones de recopilación.
Para crear 
CharSeq debes hacer lo siguiente.
 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. Establecer
Esta sección discute las diversas implementaciones de 
Set en la biblioteca de colección. Una característica única de la estructura de datos 
Set es que no permite valores duplicados.
Hay varias implementaciones de 
Set . El principal es 
HashSet . 
TreeSet no permite elementos duplicados y se puede ordenar. 
LinkedHashSet conserva el orden de inserción de los elementos.
Echemos un vistazo más de cerca a estas implementaciones una tras otra.
5.1. Hashset
HashSet tiene métodos de fábrica estáticos para crear nuevas instancias. Algunos de los cuales estudiamos anteriormente en este artículo, por ejemplo 
of() , 
ofAll() y variaciones en los métodos 
range() .
La diferencia entre los dos 
conjuntos se puede obtener utilizando el método 
diff() . Además, los métodos 
union() e 
intersect() devuelven la unión e intersección de dos 
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)); 
También podemos realizar operaciones básicas, como agregar y eliminar 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")); 
La implementación de 
HashSet se basa en el 
trie mapeado de matriz de Hash (HAMT) , que cuenta con un rendimiento superior en comparación con el 
HashTable habitual y su estructura lo hace adecuado para soportar colecciones persistentes.
5.2. Conjunto de árboles
TreeSet es una implementación de la interfaz 
SortedSet . Almacena un conjunto de elementos ordenados y se implementa utilizando árboles de búsqueda binarios. Todas sus operaciones se realizan durante el tiempo 
O (log n) .
Por defecto, los elementos 
TreeSet se ordenan en su orden natural.
SortedSet un 
SortedSet usando un orden de clasificación 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 los elementos de forma personalizada, pase una instancia de 
Comparator al crear 
TreeSet . También puede crear una cadena a partir de un 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
Las colecciones Vavr también tienen una implementación inmutable de 
BitSet . La interfaz 
BitSet extiende la interfaz 
SortedSet . 
BitSet se puede crear utilizando métodos estáticos en 
BitSet.Builder .
Al igual que con otras implementaciones de la estructura de datos 
Set , 
BitSet no le permite agregar registros duplicados a un conjunto.
Hereda métodos de manipulación de la interfaz 
Traversable . Tenga en cuenta que es diferente de 
java.util.BitSet de la biblioteca estándar de Java. 
BitSet datos de 
BitSet no pueden contener valores de 
String .
Considere crear una instancia de 
BitSet utilizando el método de fábrica 
of() :
 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 seleccionar los primeros cuatro elementos de 
BitSet utilizamos el 
takeUntil() . La operación devolvió una nueva instancia. Tenga en cuenta que el método 
takeUntil() se define en la interfaz 
Traversable , que es la interfaz principal para 
BitSet .
Otros métodos y operaciones descritos anteriormente definidos en la interfaz 
Traversable también se aplican a 
BitSet .
6. Mapa
Map es una estructura de datos clave-valor. 
Map in Vavr es inmutable y tiene implementaciones para 
HashMap , 
TreeMap y 
LinkedHashMap .
Típicamente, 
los contratos de 
mapeo no permiten claves duplicadas, mientras que los valores duplicados mapeados a claves diferentes pueden serlo.
6.1. Hashmap
HashMap es una implementación de la interfaz de 
Map inmutable. Almacena pares clave-valor utilizando un hash de claves.
Map en Vavr usa 
Tuple2 para representar pares clave-valor en lugar del 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()); 
Al igual que 
HashSet , la implementación de 
HashMap basa en el 
trie mapeado de matriz de Hash (HAMT) , lo que conduce a un tiempo constante para casi todas las operaciones.
Los elementos del 
mapa se pueden filtrar por clave utilizando el método 
filterKeys() o por valor utilizando el método 
filterValues() . Ambos métodos toman 
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")); 
También puede transformar elementos del 
map() utilizando el método 
map() . Por ejemplo, vamos a convertir 
map1 a 
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. Mapa de árbol
TreeMap es una implementación de la interfaz 
SortedMap . Al igual que con 
TreeSet , se 
TreeSet una instancia personalizada de 
Comparator para personalizar la clasificación de los elementos de 
TreeMap .
SortedMap la creación de 
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 defecto, 
TreeMap entradas de 
TreeMap se ordenan en orden de clave natural. Sin embargo, puede especificar el 
Comparator que se utilizará para ordenar:
 TreeMap < Integer, String > treeMap2 = TreeMap.of(Comparator.reverseOrder(), 3, "three", 6, "six", 1, "one"); assertEquals(treeMap2.keySet().mkString(), "631"); 
Como en el caso de 
TreeSet , la implementación de 
TreeMap también se crea utilizando el árbol, por lo tanto, sus operaciones tienen tiempo 
O (log n) . El 
map.get(key) devuelve 
Option , que contiene el valor de la clave de 
mapa especificada.
7. compatibilidad Java
La API de Vavr Collection es totalmente compatible con Java Collection Framework. Veamos cómo se hace esto en la práctica.
7.1. Convertir de Java a Vavr
Cada implementación de colección en Vavr tiene una fábrica estática del método 
ofAll() que acepta 
java.util.Iterable . Esto le permite crear una colección 
Vavr a partir de una colección 
Java . Del mismo modo, otro método de fábrica de 
ofAll() acepta directamente Java 
Stream .
Para convertir una 
List Java en una 
List inmutable:
 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); 
Otra función útil es 
collector() , que se puede usar junto con 
Stream.collect() para obtener la colección 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. Convertir de Vavr a Java
La interfaz 
Value tiene muchos métodos para convertir de un tipo 
Vavr a un tipo 
Java . Estos métodos tienen el formato 
toJavaXXX() .
Considere un par de ejemplos:
 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()); 
También podemos usar Java 8 
Collectors para recopilar elementos de las colecciones 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. Vistas de colecciones de Java
Además, la biblioteca proporciona las llamadas 
vistas de colección que funcionan mejor cuando se convierten en colecciones Java. Los métodos de transformación en la sección anterior iteran sobre (iteran) todos los elementos para crear una colección Java.
Las vistas, por otro lado, implementan interfaces Java estándar y delegan llamadas de método a la colección base de Vavr.
Al momento de escribir esto, solo se admite la vista de 
List . Cada colección secuencial tiene dos métodos: uno para crear una representación inmutable, el otro para mutable.
Llamar a métodos para cambiar en una vista inmutable 
UnsupportedOperationException .
Veamos un ejemplo:
 @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 crear una vista inmutable:
 java.util.List < Integer > javaList = List.of(1, 2, 3) .asJavaMutable(); javaList.add(4); assertEquals(4, javaList.get(3).intValue()); 
8. Conclusiones
En este tutorial, aprendimos sobre las diversas estructuras de datos funcionales proporcionadas por la API de Vavr Collections. También hay métodos API útiles y productivos que se pueden encontrar en el 
documento de Java y en 
la Guía del 
usuario de Vavr Collections.
Finalmente, es importante tener en cuenta que la biblioteca también define 
Try , 
Option , 
Either y 
Future , que amplían la interfaz 
Value y, como resultado, implementan la interfaz Java 
Iterable . Esto significa que en algunas situaciones pueden comportarse como colecciones.
El código fuente completo para todos los ejemplos en este artículo se puede encontrar en 
Github .
Materiales adicionales:
habr.com/en/post/421839www.baeldung.com/vavrTraducido por 
@middle_java