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
接口是一个顺序数据结构。 这是
List
,
Stream
,
Queue
,
Array
,
Vector
和
CharSeq
的父接口。 所有这些数据结构都有自己独特的属性,我们将在下面讨论。
4.1。 清单
List
是经过大量计算(热切评估,一旦其操作数的值已知就执行操作)的元素序列,这些元素扩展了
LinearSeq
接口。
持久
List
使用
head和
tail递归构造的:
- 头是第一要素
- 尾巴 -包含其余元素的列表(此列表也由头和尾组成)
List
API包含可用于创建
List
静态工厂方法。 您可以使用静态
of()
方法从一个或多个对象创建
List
的实例。
您还可以使用static
empty()
方法创建一个空
List
并使用
ofAll()
方法创建一个
Iterable
的
List
:
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
包含用于拆分列表的逻辑,并返回包含两个元素的
Map
:
true
和
false
。
true
键映射到满足
Function
指定条件
List
元素
List
。
false
键映射到不满足此条件
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
是不可变的,可以为
Empty
或
Cons
。
Cons
由流的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()
。 此方法返回长度为
n
的
Stream
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。 向量
Vector
是
Array
和
List
之间的交叉,提供了另一个元素索引的序列,允许在恒定时间内进行随机访问和修改:
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
有多种实现。 主要的是
HashSet
。
TreeSet
不允许重复的元素,并且可以排序。
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。 树集
不可变的
TreeSet
是
SortedSet
接口的实现。 它存储一组排序后的元素,并使用二进制搜索树实现。 它的所有操作都在
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
是不可变的,并且具有
HashMap
,
TreeMap
和
LinkedHashMap
。
通常,
地图协定不允许重复键,而映射到不同键的重复值则可以。
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。 树状图
不可变的
TreeMap
是
SortedMap
接口的实现。 与
TreeSet
,
Comparator
的自定义实例用于自定义
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");
与
TreeSet
,
TreeMap
实现也是使用树创建的,因此,其操作的时间为
O(log n) 。
map.get(key)
方法返回
Option
,其中包含指定
地图键的值。
7. Java兼容性
Vavr Collection API与Java 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方法。
最后,必须注意,该库还定义了
Try
,
Option
,
Either
和
Future
,它们扩展了
Value
接口,并因此实现了Java
Iterable
接口。 这意味着它们在某些情况下的行为可能类似于集合。
可以在
Github上找到本文所有示例的完整源代码。
其他材料:
habr.com/en/post/421839www.baeldung.com/vavr由
@middle_java翻译