Guide to Collections API in Vavr – Vavr中的集合API指南

最后修改: 2017年 9月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The Vavr library, formerly known as Javaslang, is a functional library for Java. In this article, we explore its powerful collections API.

Vavr库,以前被称为Javaslang,是一个Java的函数式库。在这篇文章中,我们将探讨其强大的集合API。

To get more information about this library, please read this article.

要获得有关这个库的更多信息,请阅读这篇文章

2. Persistent Collections

2.持久性集合

A persistent collection when modified produces a new version of the collection while preserving the current version.

一个持久化的集合在修改时产生一个新版本的集合,同时保留当前的版本。

Maintaining multiple versions of the same collection might lead to inefficient CPU and memory usage. However, the Vavr collection library overcomes this by sharing data structure across different versions of a collection.

维护同一集合的多个版本可能会导致CPU和内存的低效率使用。然而,Vavr集合库通过在一个集合的不同版本中共享数据结构来克服这个问题。

This is fundamentally different from Java’s unmodifiableCollection() from the Collections utility class, which merely provides a wrapper around an underlying collection.

这与Java的unmodifiableCollection()Collections实用类中的unmodifiableCollection()根本不同,后者只是为底层集合提供一个包装。

Trying to modify such a collection results in UnsupportedOperationException instead of a new version being created. Moreover, the underlying collection is still mutable through its direct reference.

试图修改这样的集合会导致UnsupportedOperationException而不是创建一个新的版本。此外,底层集合仍然可以通过其直接引用而变异。

3. Traversable

3.可穿越

Traversable is the base type of all Vavr collections – this interface defines methods that are shared among all data structures.

Traversable是所有Vavr集合的基础类型–这个接口定义了所有数据结构中共享的方法。

It provides some useful default methods such as size(), get(), filter(), isEmpty() and others which are inherited by sub-interfaces.

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

Let’s explore the collections library further.

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

4. Seq

4.Seq

We’ll start with sequences.

我们将从序列开始。

The Seq interface represents sequential data structures. It is the parent interface for List, Stream, Queue, Array, Vector, and CharSeq. All these data structures have their own unique properties which we’ll be exploring below.

Seq接口表示顺序数据结构。它是ListStreamQueueArrayVectorCharSeq的父接口。所有这些数据结构都有自己独特的属性,我们将在下面进行探讨。

4.1. List

4.1.列表

A List is an eagerly-evaluated sequence of elements extending the LinearSeq interface.

A List是一个扩展了LinearSeq接口的急切评价的元素序列。

Persistent Lists are formed recursively from a head and a tail:

持久性列表是由头和尾递归形成的。

  • Head – the first element
  • Tail – a list containing remaining elements (that list is also formed from a head and a tail)

There are static factory methods in the List API that can be used for creating a List. We can use the static of() method to create an instance of List from one or more objects.

List API中有一些静态工厂方法,可用于创建List。我们可以使用静态的of()方法来从一个或多个对象中创建List的实例。

We can also use the static empty() to create an empty List and ofAll() to create a List from an Iterable type:

我们还可以使用静态的empty()来创建一个空的ListofAll()来从Iterable类型创建一个List

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

Let’s take a look at some examples on how to manipulate lists.

让我们来看看一些关于如何操作列表的例子。

We can use the drop() and its variants to remove first N elements:

我们可以使用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) removes n number of elements from the list starting from the first element while the dropRight() does the same starting from the last element in the list.

drop(int n)从列表中的第一个元素开始删除n个元素,而dropRight()从列表的最后一个元素开始做同样的事情。

dropUntil() continues removing elements from the list until the predicate evaluates to true whereas the dropWhile() continues dropping elements while the predicate is true.

dropUntil()继续从列表中删除元素,直到谓词评估为真,而dropWhile()在谓词为真时继续删除元素。

There’s also dropRightWhile() and dropRightUntil() that starts removing elements from the right.

还有dropRightWhile()dropRightUntil(),开始从右边移除元素。

Next, take(int n) is used to grab elements from a list. It takes n number of elements from the list and then stops. There’s also a takeRight(int n) that starts taking elements from the end of the list:

接下来,take(int n)被用来从一个列表中抓取元素。它从列表中获取n个元素,然后停止。还有一个takeRight(int n),它从列表的末端开始抓取元素。

List list5 = list.take(1);                       
assertEquals(list5.single(), "Java");            
                                                 
List list6 = list.takeRight(1);                  
assertEquals(list6.single(), "JAVA");            
                                                 
List list7 = list.takeUntil(s -> s.length() > 6);
assertEquals(list7.size(), 3);

Finally, takeUntil() continues taking elements from the list until the predicate is true. There’s a takeWhile() variant that takes a predicate argument as well.

最后,takeUntil()继续从列表中获取元素,直到谓词为真。还有一个 takeWhile() 变体,它也需要一个谓词参数。

Moreover, there are other useful methods in the API, e.g., actually the distinct() that returns a list of non-duplicate elements as well as the distinctBy() that accepts a Comparator to determine equality.

此外,API中还有其他有用的方法,例如,实际上,distinct()返回一个不重复的元素列表,以及distinctBy()接受一个Comparator来确定平等。

Very interestingly, there’s also the intersperse() that inserts an element in between every element of a list. It can be very handy for String operations:

非常有趣的是,还有一个 intersperse()可以在列表的每个元素之间插入一个元素。它对字符串操作非常方便。

List list8 = list
  .distinctBy((s1, s2) -> s1.startsWith(s2.charAt(0) + "") ? 0 : 1);
assertEquals(list8.size(), 2);

String words = List.of("Boys", "Girls")
  .intersperse("and")
  .reduce((s1, s2) -> s1.concat( " " + s2 ))
  .trim();  
assertEquals(words, "Boys and Girls");

Want to divide a list into categories? Well, there’s an API for that too:

想把一个列表分成几类?嗯,这也有一个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);

The group(int n) divides a List into groups of n elements each. The groupdBy() accepts a Function that contains the logic for dividing the list and returns a Map with two entries – true and false.

group(int n)List 分成每组n 个元素的组。groupdBy() 接受一个Function,其中包含划分列表的逻辑,并返回一个有两个条目的Maptruefalse

The true key maps to a List of elements that satisfy the condition specified in the Function; the false key maps to a List of elements that do not.

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

As expected, when mutating a List, the original List is not actually modified. Instead, a new version of the List is always returned.

正如预期的那样,当突变一个List时,原始的List实际上没有被修改。相反,一个新版本的List总是被返回。

We can also interact with a List using stack semantics – last-in-first-out (LIFO) retrieval of elements. To this extent, there are API methods for manipulating a stack such as peek(), pop() and push():

我们还可以使用堆栈语义与List进行交互–元素的后进先出(LIFO)检索。在这种情况下,有一些 API 方法用于操作堆栈,如 peek(), pop()push()

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

The pushAll() function is used to insert a range of integers onto the stack, while the peek() is used to get the head of the stack. There’s also the peekOption() that can wrap the result in an Option object.

pushAll()函数用于在堆栈中插入一个整数范围,而peek()则用于获取堆栈的头部。还有一个peekOption()可以将结果包裹在一个Option对象中。

There are other interesting and really useful methods in the List interface that are neatly documented in the Java docs.

List接口中还有其他有趣的、真正有用的方法,这些方法在Java docs中都有整齐的记录。

4.2. Queue

4.2.队列

An immutable Queue stores elements allowing a first-in-first-out (FIFO) retrieval.

一个不可变的Queue存储元素,允许先入先出(FIFO)的检索。

A Queue internally consists of two linked lists, a front List, and a rear List. The front List contains the elements that are dequeued, and the rear List contains the elements that are enqueued.

一个Queue内部由两个链接列表组成,一个是前List,另一个是后List。前面的List包含被排队的元素,而后面的List包含被排队的元素。

This allows enqueue and dequeue operations to perform in O(1). When the front List runs out of elements, front and rear List’s are swapped, and the rear List is reversed.

这使得enqueuedequeue操作可以在O(1)中执行。当前面的List元素用完时,前面和后面的List被交换,后面的List被反转。

Let’s create a queue:

我们来创建一个队列。

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

The dequeue function removes the head element from the Queue and returns a Tuple2<T, Q>. The tuple contains the head element that has been removed as the first entry and the remaining elements of the Queue as the second entry.

dequeue函数从Queue中移除头部元素并返回一个Tuple2<T, Q>。该元组包含被移除的头部元素作为第一个条目,以及Queue的其余元素作为第二个条目。

We can use the combination(n) to get all the possible N combinations of elements in the Queue:

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

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

Again, we can see that the original Queue is not modified while enqueuing/dequeuing elements.

同样,我们可以看到,原始的Queue在排队/等待元素时没有被修改。

4.3. Stream

4.3.

A Stream is an implementation of a lazy linked list and is quite different from java.util.stream. Unlike java.util.stream, the Vavr Stream stores data and is lazily evaluating next elements.

Stream是懒惰链接列表的实现,与java.util.stream有很大区别。与java.util.stream不同,VavrStream存储数据并懒惰地评估下一个元素。

Let’s say we have a Stream of integers:

假设我们有一个整数的Stream

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

Printing the result of s.toString() to the console will only show Stream(2, ?). This means that it is only the head of the Stream that has been evaluated while the tail has not been evaluated.

s.toString()的结果打印到控制台将只显示Stream(2, ?)。这意味着只有Stream的头部被评估了,而尾部没有被评估。

Invoking s.get(3) and subsequently displaying the result of s.tail() returns Stream(1, 3, 4, ?). On the contrary, without invoking s.get(3) first which causes the Stream to evaluate the last element – the result of s.tail() will only be Stream(1, ?). This means just the first element of the tail has been evaluated.

调用s.get(3)并随后显示s.tail()的结果会返回Stream(1, 3, 4, ?)。相反,如果不首先调用s.get(3)-,导致Stream评估最后一个元素–s.tail()的结果将只是Stream(1, ?)。这意味着只有尾部的第一个元素被评估了。

This behaviour can improve performance and makes it possible to use Stream to represent sequences that are (theoretically) infinitely long.

这种行为可以提高性能,并使得使用Stream来表示(理论上)无限长的序列成为可能。

Vavr Stream is immutable and may be Empty or Cons. A Cons consists of a head element and a lazy computed tail Stream. Unlike a List, for a Stream, only the head element is kept in memory. The tail elements are computed on demand.

Vavr Stream 是不可变的,可以是EmptyCons。一个Cons由一个头部元素和一个懒得计算的尾部Stream组成。与List不同,对于Stream,只有头部元素被保存在内存中。尾部元素是按需计算的。

Let’s create a Stream of 10 positive integers and compute the sum of the even numbers:

让我们创建一个包含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);

As opposed to Java 8 Stream API, Vavr’s Stream is a data structure for storing a sequence of elements.

相对于Java 8的Stream API,Vavr的Stream是一个用于存储元素序列的数据结构。

Thus, it has methods like get(), append(), insert() and others for manipulating its elements. The drop(), distinct() and some other methods considered earlier are also available.

因此,它有像get()append()insert()等方法来操作其元素。还有drop()distinct()和其他一些先前考虑的方法。

Finally, let’s quickly demonstrate the tabulate() in a Stream. This method returns a Stream of length n, which contains elements that are the result of applying a function:

最后,让我们快速演示一下tabulate()Stream中的作用。这个方法返回一个长度为nStream,其中包含的元素是应用一个函数的结果。

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

We can also use the zip() to generate a Stream of Tuple2<Integer, Integer>, which contains elements that are formed by combining two Streams:

我们还可以使用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

4.4.数组

An Array is an immutable, indexed, sequence that allows efficient random access. It is backed by a Java array of objects. Essentially, it is a Traversable wrapper for an array of objects of type T.

Array是一个不可变的、有索引的、允许有效随机访问的序列。它由一个Java对象的数组支持。从本质上讲,它是Traversable类型的T对象数组的封装器。

We can instantiate an Array by using the static method of(). We can also generate a range elements by using the static range() and rangeBy() methods. The rangeBy() has a third parameter that let us define the step.

我们可以通过使用静态方法of()来实例化Array。我们还可以通过使用静态的range()rangeBy()方法生成一个范围元素。rangeBy()有一个第三个参数,让我们定义步骤。

The range() and rangeBy() methods will only generate elements starting from the start value to end value minus one. If we need to include the end value we can use either the rangeClosed() or rangeClosedBy():

range()rangeBy()方法将只生成从开始值到结束值减1的元素。如果我们需要包括结束值,我们可以使用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(rArray3.size(), 3);

Let’s manipulate the elements by index:

让我们通过索引来操作这些元素。

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(array2.get(0).intValue(), 5);

4.5. Vector

4.5.矢量

A Vector is a kind of in-between Array and List providing another indexed sequence of elements that allows both random access and modification in constant time:

Vector是一种介于ArrayList之间的东西,它提供了另一个元素的索引序列,允许随机访问和在恒定时间内修改。

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

4.6.CharSeq

CharSeq is a collection object to express a sequence of primitive characters. It is essentially a String wrapper with the addition of collection operations.

CharSeq是一个集合对象,用来表达原始字符的序列。它本质上是一个String包装器,增加了集合操作。

To create a CharSeq:

要创建一个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

5.设置

In this section, we elaborate on various Set implementations in the collections library. The unique feature of the Set data structure is that it doesn’t allow duplicate values.

在这一节中,我们将阐述集合库中各种Set的实现。Set数据结构的独特之处在于,它不允许重复的值。

There are, however, different implementations of Set – the HashSet being the basic one. The TreeSet doesn’t allow duplicate elements and can be sorted. The LinkedHashSet maintains the insertion order of its elements.

然而,Set有不同的实现–HashSet是最基本的一种。TreeSet不允许重复的元素,并且可以被排序。LinkedHashSet保持其元素的插入顺序。

Let’s have a closer look at these implementations one by one.

让我们逐一仔细看看这些实现方式。

5.1. HashSet

5.1.HashSet

HashSet has static factory methods for creating new instances – some of which we have explored previously in this article – like of(), ofAll() and variations of range() methods.

HashSet拥有用于创建新实例的静态工厂方法–其中一些我们已经在本文中探讨过了–比如of()ofAll()range()方法的变化。

We can get the difference between two sets by using the diff() method. Also, the union() and intersect() methods return the union set and intersection set of the two sets:

我们可以通过使用diff()方法来获得两个集合的差异。另外,union()intersect()方法返回两个集合的union集和intersection集。

HashSet<Integer> set0 = HashSet.rangeClosed(1,5);
HashSet<Integer> set1 = HashSet.rangeClosed(3, 6);

assertEquals(set0.union(set1), HashSet.rangeClosed(1,6));
assertEquals(set0.diff(set1), HashSet.rangeClosed(1,2));
assertEquals(set0.intersect(set1), HashSet.rangeClosed(3,5));

We can also perform basic operations such as adding and removing elements:

我们还可以进行基本的操作,如添加和删除元素。

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

The HashSet implementation is backed by a Hash array mapped trie (HAMT), which boasts a superior performance when compared to an ordinary HashTable and its structure makes it suitable for backing a persistent collection.

HashSet的实现由哈希数组映射三角形(HAMT)支持,与普通的HashTable相比,它拥有卓越的性能,其结构使它适合支持一个持久的集合。

5.2. TreeSet

5.2.TreeSet

An immutable TreeSet is an implementation of the SortedSet interface. It stores a Set of sorted elements and is implemented using binary search trees. All its operations run in O(log n) time.

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

By default, elements of a TreeSet are sorted in their natural order.

默认情况下,TreeSet中的元素是按其自然顺序排序的。

Let’s create a SortedSet using natural sorting order:

让我们使用自然的排序顺序创建一个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());

To order elements in a customized manner, pass a Comparator instance while creating a TreeSet. We can also generate a string from the set elements:

要以自定义的方式排列元素,在创建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. BitSet

5.3.BitSet

Vavr collections also contain an immutable BitSet implementation. The BitSet interface extends the SortedSet interface. BitSet can be instantiated using static methods in BitSet.Builder.

Vavr集合还包含一个不可变的BitSet实现。BitSet接口扩展了SortedSet接口。BitSet可以使用BitSet.Builder中的静态方法进行实例化。

Like other implementations of the Set data structure, BitSet does not allow duplicate entries to be added to the set.

像其他Set数据结构的实现一样,BitSet不允许重复的条目被添加到集合中。

It inherits methods for manipulation from the Traversable interface. Note that it is different from the java.util.BitSet in the standard Java library. BitSet data can’t contain String values.

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

Let’s see how to create a BitSet instance using the factory method of():

让我们看看如何使用工厂方法of()来创建一个BitSet实例。

BitSet<Integer> bitSet = BitSet.of(1,2,3,4,5,6,7,8);
BitSet<Integer> bitSet1 = bitSet.takeUntil(i -> i > 4);
assertEquals(bitSet1.size(), 4);

We use the takeUntil() to select the first four elements of BitSet. The operation returned a new instance. Take note that the takeUntil() is defined in the Traversable interface, which is a parent interface of BitSet.

我们使用 takeUntil() 来选择 BitSet. 的前四个元素,该操作返回一个新的实例。请注意,takeUntil()被定义在Traversable接口中,它是BitSet.的父接口。

Other methods and operations demonstrated above, that are defined in the Traversable interface, are also applicable to BitSet as well.

上面演示的在 Traversable 接口中定义的其它方法和操作也适用于 BitSet

6. Map

6.地图

A map is a key-value data structure. Vavr’s Map is immutable and has implementations for HashMap, TreeMap, and LinkedHashMap.

地图是一种键值数据结构。Vavr的Map是不可变的,并且有HashMapTreeMapLinkedHashMap的实现。

Generally, map contracts don’t allow duplicate keys – though there may be duplicate values mapped to different keys.

一般来说,地图合约不允许有重复的键–尽管可能有重复的值映射到不同的键。

6.1. HashMap

6.1.HashMap

A HashMap is an implementation of an immutable Map interface. It stores key-value pairs using the hash code of the keys.

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

Vavr’s Map uses Tuple2 to represent key-value pairs instead of a traditional Entry type:

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

Similar to HashSet, a HashMap implementation is backed by a hash array mapped trie (HAMT) resulting in constant time for almost all operations.

HashSet类似,HashMap的实现由一个哈希数组映射的三角形(HAMT)支持,导致几乎所有操作的时间不变。

We can filter map entries by keys, using the filterKeys() method or by values, using the filterValues() method. Both methods accept a Predicate as an argument:

我们可以使用filterKeys()方法按键过滤地图条目,或者使用filterValues()方法按值过滤。这两种方法都接受一个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> fMap2
  = map1.filterValues(v -> v.contains("3"));
assertEquals(fMap2.size(), 1);
assertTrue(fMap2.containsValue("val3"));

We can also transform map entries by using the map() method. Let’s, for example, transform map1 to a Map<String, Integer>:

我们还可以通过使用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. TreeMap

6.2.TreeMap

An immutable TreeMap is an implementation of the SortedMap interface. Similar to TreeSet, a Comparator instance is used to custom sort elements of a TreeMap.

一个不可变的TreeMapSortedMap接口的一个实现。与TreeSet类似,Comparator实例被用来对TreeMap的元素进行自定义排序。

Let’s demonstrate the creation of a 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());

By default, entries of TreeMap are sorted in the natural order of the keys. We can, however, specify a Comparator that will be used for sorting:

默认情况下,TreeMap的条目是按照键的自然顺序排序的。然而,我们可以指定一个比较器,它将被用于排序。

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

As with TreeSet, a TreeMap implementation is also modeled using a tree, hence its operations are of O(log n) time. The map.get(key) returns an Option that wraps a value at the specified key in the map.

TreeSet一样,TreeMap的实现也是使用树来建模的,因此其操作的时间为O(log n)。map.get(key)返回一个Option,它在地图中的指定键上包装了一个值。

7. Interoperability With Java

7.与Java的互操作性

The collection API is fully interoperable with Java’s collection framework. Let’s see how this is done in practice.

集合API与Java的集合框架是完全互通的。让我们看看在实践中是如何做到这一点的。

7.1. Java to Vavr Conversion

7.1.Java到Vavr的转换

Each collection implementation in Vavr has a static factory method ofAll() that takes a java.util.Iterable. This allows us to create a Vavr collection out of a Java collection. Likewise, another factory method ofAll() takes a Java Stream directly.

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

To convert a Java List to an immutable List:

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

Another useful function is the collector() that can be used in conjunction with Stream.collect() to obtain a Vavr collection:

另一个有用的函数是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 to Java Conversion

7.2.Vavr到Java的转换

Value interface has many methods to convert a Vavr type to a Java type. These methods are of the format toJavaXXX().

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

Let’s address a couple of examples:

让我们来谈一谈几个例子。

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

We can also use Java 8 Collectors to collect elements from Vavr collections:

我们还可以使用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 Collection Views

7.3.Java集合视图

Alternatively, the library provides so-called collection views that perform better when converting to Java collections. The conversion methods from the previous section iterate through all the elements to build a Java collection.

另外,该库提供了所谓的集合视图,在转换为Java集合时表现更好。上一节的转换方法会遍历所有的元素来建立一个Java集合。

Views, on the other hand, implement standard Java interfaces and delegate method calls to the underlying Vavr collection.

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

As of this writing, only the List view is supported. Each sequential collection has two methods, one to create an immutable view and another for a mutable view.

截至目前,只有List视图被支持。每个顺序集合有两个方法,一个用于创建不可变的视图,另一个用于创建可变的视图。

Calling mutator methods on immutable view results in an UnsupportedOperationException.

在不可变的视图上调用mutator方法会导致UnsupportedOperationException

Let’s look at an example:

我们来看看一个例子。

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

To create an immutable view:

要创建一个不可变的视图。

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

assertEquals(4, javaList.get(3).intValue());

8. Conclusion

8.结论

In this tutorial, we’ve learned about various functional data structures provided by Vavr’s Collection API. There are more useful and productive API methods that can be found in Vavr’s collections JavaDoc and the user guide.

在本教程中,我们已经了解了Vavr的集合API所提供的各种功能数据结构。在Vavr的集合JavaDoc用户指南中,可以找到更多有用的、富有成效的API方法。

Finally, it’s important to note that the library also defines Try, Option, Either, and Future that extend the Value interface and as a consequence implement Java’s Iterable interface. This implies that they can behave as a collection in some situations.

最后,需要注意的是,该库还定义了TryOptionEitherFuture,它们扩展了Value接口,因此也实现了Java的Iterable接口。这意味着它们在某些情况下可以表现得像一个集合。

The complete source code for all the examples in this article can be found over on Github.

本文中所有例子的完整源代码可以在Github上找到over