1. 概述
Vavr 库(前身为 Javaslang)是一个为 Java 设计的函数式库。本文将深入探索其强大的集合 API。
想了解更多关于该库的信息,请阅读这篇文章。
2. 持久化集合
持久化集合在修改时会产生新版本,同时保留当前版本。维护同一集合的多个版本可能导致 CPU 和内存使用效率低下,但 Vavr 集合库通过在不同版本间共享数据结构解决了这个问题。
这与 Java 中 Collections
工具类的 unmodifiableCollection()
完全不同——后者只是对底层集合的包装。尝试修改这样的集合会抛出 UnsupportedOperationException
,而不是创建新版本。而且,底层集合仍然可以通过直接引用被修改。
3. Traversable
Traversable
是所有 Vavr 集合的基类型,定义了所有数据结构共享的方法。它提供了一些有用的默认方法,如 size()
、get()
、filter()
、isEmpty()
等,这些方法会被子接口继承。
让我们进一步探索集合库。
4. Seq
我们从序列开始。Seq
接口表示顺序数据结构,是 List
、Stream
、Queue
、Array
、Vector
和 CharSeq
的父接口。这些数据结构各有独特特性,下面将逐一探索。
4.1. List
List
是一个急切求值的元素序列,继承自 LinearSeq
接口。持久化 List
由头(head)和尾(tail)递归构成:
- 头:第一个元素
- 尾:包含剩余元素的列表(同样由头和尾构成)
List
API 提供静态工厂方法创建实例:
of()
:从一个或多个对象创建List
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()
:从列表末尾移除dropUntil()
:移除元素直到谓词为真dropWhile()
:当谓词为真时持续移除
⚠️ 还有 dropRightWhile()
和 dropRightUntil()
从右侧开始移除。
✅ 使用 take(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);
takeUntil()
:获取元素直到谓词为真takeWhile()
:当谓词为真时持续获取
✅ 其他实用方法:
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");
distinctBy()
:通过比较器去重intersperse()
:在元素间插入分隔符(字符串操作利器)
✅ 分组操作:
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)
:将列表分成每组 n 个元素groupBy()
:按条件分组,返回包含true
和false
键的Map
❌ 修改 List
时,原始列表不会被修改,而是返回新版本。
✅ 栈语义操作(LIFO):
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()
:获取栈顶元素peekOption()
:将结果包装在Option
中
更多方法详见 Java 文档。
4.2. Queue
不可变 Queue
实现先进先出(FIFO)检索。内部由两个链表组成:前部列表(出队元素)和后部列表(入队元素)。这使得入队和出队操作时间复杂度为 O(1)。当前部列表耗尽时,前后列表会交换并反转后部列表。
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()
:移除队头元素,返回包含移除元素和剩余队列的元组combination(n)
:获取所有可能的 N 元素组合:Queue<Queue<Integer>> queue1 = queue.combinations(2); assertEquals(queue1.get(2).toCharSeq(), CharSeq.of("23"));
❌ 入队/出队操作不会修改原始队列。
4.3. Stream
Stream
是惰性链表的实现,与 java.util.stream
完全不同。Vavr Stream
存储数据并惰性求值后续元素。
Stream<Integer> s = Stream.of(2, 1, 3, 4);
打印 s.toString()
只显示 Stream(2, ?)
,表示仅头部被求值。调用 s.get(3)
后,s.tail()
返回 Stream(1, 3, 4, ?)
。这种特性可提升性能,并支持表示无限序列。
Vavr Stream
不可变,可以是 Empty
或 Cons
(包含头部元素和惰性计算的尾部流)。与 List
不同,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()
等操作。
✅ tabulate()
生成应用函数的流:
Stream<Integer> s1 = Stream.tabulate(5, (i)-> i + 1);
assertEquals(s1.get(2).intValue(), 3);
✅ zip()
合并两个流:
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
Array
是不可变、可索引的序列,支持高效随机访问,由 Java 对象数组支持。本质上是 T
类型对象数组的 Traversable
包装器。
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);
range()
:生成 [start, end-1] 范围rangeClosed()
:生成 [start, end] 范围rangeClosedBy()
:带步长的范围生成
✅ 按索引操作:
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
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 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
HashSet
通过静态工厂方法创建实例(如 of()
、ofAll()
和 range()
变体):
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));
diff()
:集合差集union()
:并集intersect()
:交集
✅ 基本操作:
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
由 哈希数组映射字典树(HAMT) 支持,性能优于普通 HashTable
,适合持久化集合。
5.2. TreeSet
不可变 TreeSet
是 SortedSet
的实现,使用二叉搜索树存储有序元素,所有操作时间复杂度为 O(log n)。默认按自然顺序排序:
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());
✅ 自定义排序:
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
Vavr 提供不可变 BitSet
实现,继承自 SortedSet
。通过 BitSet.Builder
的静态方法实例化,不允许重复值:
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);
⚠️ 与标准库的 java.util.BitSet
不同,Vavr BitSet
不能包含 String
值。
6. Map
Map
是键值对数据结构。Vavr 的 Map
不可变,提供 HashMap
、TreeMap
和 LinkedHashMap
实现。键不允许重复,但值可以重复。
6.1. HashMap
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
由 HAMT 支持,几乎所有操作都是常量时间。
✅ 按键或值过滤:
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"));
✅ 转换条目:
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
不可变 TreeMap
是 SortedMap
的实现,使用 Comparator
自定义排序:
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<Integer, String> treeMap2 =
TreeMap.of(Comparator.reverseOrder(), 3,"three", 6, "six", 1, "one");
assertEquals(treeMap2.keySet().mkString(), "631");
⚠️ TreeMap
基于树实现,操作时间复杂度为 O(log n)。map.get(key)
返回包装值的 Option
。
7. 与 Java 互操作
集合 API 与 Java 集合框架完全互操作。
7.1. Java 转 Vavr
每个 Vavr 集合实现都有静态工厂方法 ofAll()
,接受 java.util.Iterable
或 Java Stream
:
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);
✅ 结合 Stream.collect()
使用 collector()
:
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
接口提供 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
:
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 接口,将方法调用委托给底层 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 集合 API 提供的各种函数式数据结构。⚠️ 注意,库还定义了 Try
、Option
、Either
和 Future
,它们扩展了 Value
接口并实现 Java 的 Iterable
接口,因此在某些情况下可表现为集合。
本文所有示例的完整源代码可在 GitHub 获取。