Vavr Collections API指南

VAVR (以前称为Javaslang)是Java 8+的非盈利功能库。 它使您可以用Java编写类似于Scala的功能代码,并减少代码量并提高其质量。 图书馆网站

切下是翻译有关Vavr Collections API的信息的文章的翻译。

@middle_java翻译

上次修改的原始文章:2019年8月15日

1.概述


Vavr库(以前称为Javaslang)是Java的功能库。 在本文中,我们将探讨其强大的集合API。

有关此库的更多信息,请参见本文

2.永久收藏


持久性集合在修改后会创建该集合的新版本,而不会更改当前版本。

对同一集合的多个版本的支持会导致CPU和内存使用效率低下。 但是,Vavr集合库通过在集合的不同版本之间共享数据结构来克服此问题。

这与Java实用工具类Collections unmodifiableCollection()根本不同,后者只是为基本集合提供了一个包装器。

尝试修改这样的集合UnsupportedOperationException而不是创建新版本。 而且,基本集合仍可以通过直接链接到它而可变。

3.遍历


Traversable是所有Vavr集合的基本类型。 该接口定义了所有数据结构共有的方法。

它提供了一些有用的默认方法,例如size()get()filter()isEmpty()以及其他由子接口继承的方法。

我们将进一步探索收藏库。

4.序列


让我们从序列开始。

Seq接口是一个顺序数据结构。 这是ListStreamQueueArrayVectorCharSeq的父接口。 所有这些数据结构都有自己独特的属性,我们将在下面讨论。

4.1。 清单


List是经过大量计算(热切评估,一旦其操作数的值已知就执行操作)的元素序列,这些元素扩展了LinearSeq接口。

持久List使用headtail递归构造的:

  • 是第一要素
  • 尾巴 -包含其余元素的列表(此列表也由头和尾组成)

List API包含可用于创建List静态工厂方法。 您可以使用静态of()方法从一个或多个对象创建List的实例。

您还可以使用static empty()方法创建一个空List并使用ofAll()方法创建一个IterableList

 List < String > list = List.of( "Java", "PHP", "Jquery", "JavaScript", "JShell", "JAVA"); 

让我们看一下列表操作的一些例子。

我们可以使用drop()方法及其变体删除前N元素:

 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)从列表的第一项开始删除列表中的n个项,而dropRight()从列表的最后一项开始进行dropRight()

dropUntil()从列表中删除项目,直到谓词为true ;而dropWhile()从列表中删除项目,直到谓词为true

也有dropRightWhile()dropRightUntil()方法从右侧开始删除项目。

接下来, take(int n)从列表中检索项目。 它从列表中取出n项目,然后停止。 还有takeRight(int n) ,它从列表的末尾获取元素:

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

最后, takeUntil()从列表中获取元素,直到谓词变为true为止。 takeWhile()有一个变体,它也带有谓词参数。

此外,API还具有其他有用的方法,例如,甚至distinct()返回具有删除的重复项的元素列表,以及distinctBy()接受Comparator确定相等性。

非常有趣的是,还有intersperse() ,它在列表的每个元素之间插入一个元素。 这对于使用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"); 

要将列表分为几类? 为此,有一个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); 

group(int n)方法将List分成n元素的组。 groupdBy()方法采用一个Function ,该Function包含用于拆分列表的逻辑,并返回包含两个元素的Maptruefalse

true键映射到满足Function指定条件List元素Listfalse键映射到不满足此条件List元素List

不出所料,当更改List ,原始List实际上并未更改。 而是总是返回新版本的List

我们还可以使用堆栈的语义与List进行交互-根据“后进先出”原理(LIFO)提取元素。 从这个意义上讲,存在peek()pop()push()类的API方法用于操作堆栈:

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

pushAll()函数用于在堆栈上插入整数范围,而peek()函数用于检索堆栈的head元素。 还有一个peekOption()方法可以将结果包装在Option对象中。

List接口中还有其他有趣且非常有用的方法,这些方法已在Java文档中进行了详细介绍。

4.2。 列


不可变Queue存储元素,使您可以根据FIFO原理(先进先出)来检索它们。

Queue内部由两个链接列表组成:前List和后List 。 前List包含从队列中删除的项目,后List包含排队的项目。

这使您可以将排队和从队列中删除的操作放入复杂度O(1) 。 当List从队列中删除时在前List结束时,后List反转并成为新的前List

让我们创建一个队列:

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

dequeue()函数从Queue删除head元素,并返回Tuple2<T, Q> 。 元组的第一个元素是从队列中删除的head元素,元组的第二个元素是其余的Queue元素。

我们可以使用combination(n)获得Queue所有可能的N个元素组合:

 Queue < Queue < Integer >> queue1 = queue.combinations(2); assertEquals(queue1.get(2).toCharSeq(), CharSeq.of("23")); 

再一次,原始Queue在添加/删除队列中的项目时不会更改。

4.3。 流


Stream是延迟链接列表的实现,该列表与java.util.stream明显不同。 与java.util.stream不同, Stream Vavr存储数据并延迟计算后续元素。
假设我们有Stream整数:

 Stream < Integer > s = Stream.of(2, 1, 3, 4); 

在控制台中打印s.toString()的结果时,将仅显示Stream(2 ,?) 。 这意味着仅计算Stream头元素,而没有计算尾元素。

调用s.get(3)然后显示s.tail()的结果将返回Stream( s.tail() ?) 。 相反,如果不首先调用s.get(3) -这将使Stream计算最后一个元素-仅Stream(1 ,?)将是s.tail() 的结果。 这意味着仅计算了第一个尾部元素。

此行为可以提高性能,并允许Stream用于表示(理论上)无限长的序列。
Vavr中的Stream是不可变的,可以为EmptyConsCons由流的head元素和延迟计算的tail组成。 与List不同, Stream将head元素存储在内存中。 尾部元素根据需要进行计算。

让我们创建一个由10个正整数组成的Stream ,并计算偶数之和:

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

与Java 8中的Stream API不同,Vavr中的Stream是一种用于存储元素序列的数据结构。

因此,它具有诸如get()append()insert()等方法来操纵其元素。 drop()distinct()和前面讨论的其他一些方法。

最后,让我们快速演示Stream tabulate() 。 此方法返回长度为nStream n包含应用该函数的结果:

 Stream < Integer > s1 = Stream.tabulate(5, (i) - > i + 1); assertEquals(s1.get(2).intValue(), 3); 

我们还可以使用zip()Tuple2<Integer, Integer>创建一个Stream ,其中包含通过组合两个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。 数组


Array是不可变的索引序列,可提供有效的随机访问。 它基于Java对象数组 。 本质上,这是T型对象数组的可Traversable包装器T

您可以使用of()方法的静态方法创建Array的实例。 另外,您可以使用静态方法range()rangeBy()创建一系列元素。 rangeBy()方法具有第三个参数,可让您确定步骤。

range()rangeBy()方法将创建元素,仅从初始值到最终值减一开始。 如果需要包含最终值,可以使用rangeClosed()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); 

让我们使用索引来处理元素:

 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。 向量


VectorArrayList之间的交叉,提供了另一个元素索引的序列,允许在恒定时间内进行随机访问和修改:

 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是用于表示原始字符序列的集合对象。 从本质上讲,这是String的包装器,带有附加的收集操作。

要创建CharSeq您必须执行以下操作。

 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.设置


本节讨论集合库中Set的各种实现。 Set数据结构的独特功能是不允许重复值。

Set有多种实现。 主要的是HashSetTreeSet不允许重复的元素,并且可以排序。 LinkedHashSet保留元素的插入顺序。

让我们一个接一个地仔细研究这些实现。

5.1。 哈希集


HashSet具有用于创建新实例的静态工厂方法。 我们在本文前面已经研究了其中的一些,例如of()ofAll()range()方法的变体。

两组之间的差异可以使用diff()方法获得。 同样, union()intersect()方法返回两个集合的并集和交集:

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

我们还可以执行基本操作,例如添加和删除元素:

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

HashSet实现基于Hash数组映射的特里(HAMT) ,与普通的HashTable相比,它具有优越的性能,其结构使其适合于支持持久性集合。

5.2。 树集


不可变的TreeSetSortedSet接口的实现。 它存储一组排序后的元素,并使用二进制搜索树实现。 它的所有操作都在O(log n)时间内执行

默认情况下, TreeSet元素以其自然顺序排序。
让我们使用自然排序顺序创建一个SortedSet

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

要以自定义方式排列项目,请在创建TreeSet时传递一个Comparator实例。 您还可以根据一组元素创建一个字符串:

 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。 位集


Vavr集合还具有不可变的BitSet实现。 BitSet接口扩展了SortedSet接口。 可以使用BitSet.Builder静态方法创建BitSet
Set数据结构的其他实现一样, BitSet不允许您将重复记录添加到集合中。

它从Traversable接口继承了用于操作的方法。 请注意,它与标准Java库java.util.BitSet不同。 BitSet数据不能包含String值。

考虑使用of()工厂方法创建BitSet的实例:

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

为了选择前四个BitSet元素BitSet我们使用了takeUntil()命令。 该操作返回了一个新实例。 请注意, takeUntil()方法在Traversable接口中定义,该接口是BitSet的父接口。

以上在Traversable接口中定义的其他方法和操作也适用于BitSet

6.地图


Map是键值数据结构。 Vavr中的Map是不可变的,并且具有HashMapTreeMapLinkedHashMap

通常, 地图协定不允许重复键,而映射到不同键的重复值则可以。

6.1。 哈希图


HashMap是不可变Map接口的实现。 它使用键的哈希值存储键值对。

Vavr中的Map使用Tuple2表示键值对,而不是传统的Entry类型:

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

HashSet一样, HashMap的实现基于Hash数组映射的trie(HAMT) ,这导致几乎所有操作的时间都是恒定的。
可以通过使用filterKeys()方法的键或使用filterKeys()方法的值来过滤地图元素。 两种方法都以Predicate作为参数:

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

您还可以使用map()方法转换地图元素。 例如,将map1转换为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。 树状图


不可变的TreeMapSortedMap接口的实现。 与TreeSetComparator的自定义实例用于自定义TreeMap元素的排序。
SortedMap演示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()); 

默认情况下, TreeMap条目以自然键顺序排序。 但是,您可以指定用于排序的Comparator

 TreeMap < Integer, String > treeMap2 = TreeMap.of(Comparator.reverseOrder(), 3, "three", 6, "six", 1, "one"); assertEquals(treeMap2.keySet().mkString(), "631"); 

TreeSetTreeMap实现也是使用树创建的,因此,其操作的时间为O(log n)map.get(key)方法返回Option ,其中包含指定地图键的值。

7. Java兼容性


Vavr Collection API与Jav​​a Collection Framework完全兼容。 让我们看看如何在实践中做到这一点。

7.1。 从Java转换为Vavr


Vavr中的每个集合实现都有一个静态工厂ofAll()方法,该方法接受java.util.Iterable 。 这使您可以从Java集合创建Vavr集合。 同样,另一个ofAll()工厂方法直接接受Java Stream

要将Java List转换为不可变List

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

另一个有用的函数是collector() ,可以与Stream.collect()结合使用以获取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。 从Vavr转换为Java


Value接口具有许多从Vavr类型转换为Java类型的方法。 这些方法的格式为toJavaXXX()

考虑几个例子:

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

我们还可以使用Java 8 Collectors从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。 Java集合视图


此外,该库提供了所谓的集合视图 ,这些视图在转换为Java集合时最有效。 上一节中的转换方法遍历(迭代)所有元素以创建Java集合。

另一方面,视图实现标准的Java接口并将方法调用委托给Vavr基本集合。

在撰写本文时,仅支持List视图。 每个顺序集合都有两种方法:一种用于创建不可变表示,另一种用于可变。

调用更改不可变视图的方法将UnsupportedOperationException

让我们看一个例子:

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

要创建一个不变的视图:

 java.util.List < Integer > javaList = List.of(1, 2, 3) .asJavaMutable(); javaList.add(4); assertEquals(4, javaList.get(3).intValue()); 

8.结论


在本教程中,我们了解了Vavr Collections API提供的各种功能数据结构。 在Java文档 Vavr集合用户指南》中也可以找到有用且高效的API方法。

最后,必须注意,该库还定义了TryOptionEitherFuture ,它们扩展了Value接口,并因此实现了Java Iterable接口。 这意味着它们在某些情况下的行为可能类似于集合。

可以在Github上找到本文所有示例的完整源代码。

其他材料:
habr.com/en/post/421839
www.baeldung.com/vavr

@middle_java翻译

Source: https://habr.com/ru/post/zh-CN474402/


All Articles