Java 创建不可变集合,用户希望利用 Stream API 创建不可变的列表、集合或映射。使用 Collectors
类新增的静态方法 collectingAndThen
。函数式编程强调并行(parallelization)以及语义的清晰,倾向于尽可能使用不可变对象。
Java 创建不可变集合 问题描述
用户希望利用 Stream API 创建不可变的列表、集合或映射。
Java 创建不可变集合 解决方案
使用 Collectors
类新增的静态方法 collectingAndThen
。
Java 创建不可变集合 具体实例
函数式编程强调并行(parallelization)以及语义的清晰,倾向于尽可能使用不可变对象。Java 1.2 引入了集合框架,提供以现有集合为基础创建不可变集合的各种方法,但使用起来有些不便。
Collections
工具类定义了 unmodifiableList
、unmodifiableSet
与 unmodifiableMap
方法(以及其他以 unmodifiable
为前缀的方法),如例 4-30 所示。
例 4-30
Collections
类定义的以unmodifiable
为前缀的方法
static <T> List<T> unmodifiableList(List<? extends T> list)
static <T> Set<T> unmodifiableSet(Set<? extends T> s)
static <K,V> Map<K,V> unmodifiableMap(Map<? extends K,? extends V> m)
上述三种方法的参数分别为现有的列表、集合与映射。结果列表、集合与映射中包含的元素和参数相同,但存在一个重要的区别:所有可以修改集合的方法(如 add
或 remove
)现在都能抛出 UnsupportedOperationException
。
在 Java 8 之前,如果通过可变参数列表获取到单个值作为参数,则会生成一个不可修改的列表或集合,如例 4-31 所示。
例 4-31 创建不可修改的列表或集合(Java 8 之前)
@SafeVarargs ➊
public final <T> List<T> createImmutableListJava7(T... elements) {
return Collections.unmodifiableList(Arrays.asList(elements));
}
@SafeVarargs ➊
public final <T> Set<T> createImmutableSetJava7(T... elements) {
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(elements)));
}
➊ 用户承诺不会破坏输入数组类型。详见附录 A。
两段代码首先传入输入值,并将它们转换为 List
。第一段代码使用 unmodifiableList
包装生成的列表。而对于 Set
,第二段代码先将列表用作集合构造函数的参数,再使用 unmodifiableSet
。
借由 Java 8 引入的 Stream API,我们可以使用 Collectors
类定义的静态方法 collectingAndThen
,如例 4-32 所示。
例 4-32 创建不可修改的列表或集合(Java 8)
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
// 采用以下方法来定义类
@SafeVarargs
public final <T> List<T> createImmutableList(T... elements) {
return Arrays.stream(elements)
.collect(collectingAndThen(toList(),
Collections::unmodifiableList)); ➊
}
@SafeVarargs
public final <T> Set<T> createImmutableSet(T... elements) {
return Arrays.stream(elements)
.collect(collectingAndThen(toSet(),
Collections::unmodifiableSet)); ➊
}
➊ “终止器”对生成的集合进行包装
collectingAndThen
方法传入两个参数,一个是下游 Collector
,另一个是称为终止器(finisher)的 Function
。该方法的作用是读取输入元素,并将它们收集到 List
或 Set
,然后利用不可修改的函数包装结果集合。
将一系列输入元素转换为一个不可修改的 Map
看起来并不直观,部分原因在于很难看出哪些输入元素将作为键,哪些将作为值。例 4-334 采用实例初始化器(instance initializer),以一种很别扭的方式创建了一个不可变的 Map
。
4灵感源自 Carl Martensen 的博文“Java 9’s Immutable Collections Are Easier To Create But Use With Caution”。
例 4-33 创建不可变的
Map
Map<String, Integer> map = Collections.unmodifiableMap(
new HashMap<String, Integer>() {{
put("have", 1);
put("the", 2);
put("high", 3);
put("ground", 4);
}});
熟悉 Java 9 的读者想必已经了解,本范例中的所有问题都可以通过工厂方法 List.of
、Set.of
与 Map.of
来解决,这些方法能极大提高效率。