Java中创建不可变集合的方法及应用场景
文章目录
一、引言
在Java编程中,有时候我们需要创建一种集合,这个集合在创建之后不允许被修改,即它是不可变的、只读的。这种不可变集合在很多场景下都非常有用,例如配置信息的存储、缓存数据的固定部分等。接下来我们将详细介绍几种在Java中创建不可变集合的方法以及它们的适用场景。
二、使用Collections.unmodifiableXXX
方法
(一)方法介绍
Java的Collections
类提供了一系列用于创建不可变集合的方法,如unmodifiableList
、unmodifiableSet
、unmodifiableMap
等,分别用于创建不可变的列表、集合和映射。这些方法接收一个可变集合作为参数,并返回一个不可变的视图。
(二)代码示例
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UnmodifiableCollectionsExample {
public static void main(String[] args) {
List<String> mutableList = new ArrayList<>();
mutableList.add("apple");
mutableList.add("banana");
// 创建不可变列表
List<String> unmodifiableList = Collections.unmodifiableList(mutableList);
try {
// 下面这行代码会抛出UnsupportedOperationException
unmodifiableList.add("cherry");
} catch (UnsupportedOperationException e) {
System.out.println("不允许修改不可变集合");
}
System.out.println(unmodifiableList);
}
}
(三)原理分析
当我们使用Collections.unmodifiableList
(以列表为例)创建不可变集合时,实际上返回的是一个内部类的实例,这个内部类实现了List
接口。在这个内部类中,所有会修改集合的方法(如add
、remove
、clear
等)都被重写为抛出UnsupportedOperationException
异常。这样就保证了外部代码无法通过这个视图来修改原始的集合。
三、使用List.of
、Set.of
和Map.of
(Java 9 +)
(一)方法介绍
从Java 9开始,提供了更简洁的创建不可变集合的方法。对于列表,可以使用List.of
;对于集合,可以使用Set.of
;对于映射,可以使用Map.of
及其相关的Map.ofEntries
方法。这些方法返回的集合是不可变的。
(二)代码示例
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ImmutableCollectionsInJava9 {
public static void main(String[] args) {
List<String> immutableList = List.of("apple", "banana");
try {
// 下面这行代码会抛出UnsupportedOperationException
immutableList.add("cherry");
} catch (UnsupportedOperationException e) {
System.out.println("不允许修改不可变列表");
}
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<String, Integer> immutableMap = Map.of("key1", 1, "key2", 2);
}
}
(三)原理分析
这些方法创建的不可变集合是基于固定的元素来构建的。在内部实现中,它们返回的集合对象的实现类会禁止对集合进行修改操作。以List.of
为例,返回的List
实现类在add
、remove
等修改方法中直接抛出UnsupportedOperationException
,并且其内部的数据结构是基于固定大小的数组,不支持动态扩容等修改操作。
四、使用Guava库(第三方库)
(一)方法介绍
Guava是一个非常流行的Java第三方库,它提供了ImmutableList
、ImmutableSet
、ImmutableMap
等类用于创建不可变集合。这些类提供了丰富的方法来构建不可变集合,并且在性能和功能上都有很好的优化。
(二)代码示例
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableMap;
public class GuavaImmutableCollections {
public static void main(String[] args) {
ImmutableList<String> immutableList = ImmutableList.of("apple", "banana");
ImmutableSet<Integer> immutableSet = ImmutableSet.of(1, 2, 3);
ImmutableMap<String, Integer> immutableMap = ImmutableMap.of("key1", 1, "key2", 2);
}
}
(三)原理分析
Guava的不可变集合在创建时会对传入的元素进行拷贝或者直接使用不可变的数据结构。例如,ImmutableList
在构建时会根据传入的元素创建一个固定大小的内部数组来存储元素,并且在所有修改方法中都抛出异常。同时,Guava的不可变集合还提供了一些方便的方法,如builder
方法,可以通过链式调用的方式构建集合,并且在构建完成后返回一个不可变的集合对象。
五、为什么需要不可变集合?
(一)线程安全
在多线程环境下,不可变集合是天然的线程安全的。因为它们不能被修改,所以多个线程可以同时访问这些集合而不会出现数据不一致的问题。例如,在一个Web应用的配置管理模块中,配置信息通常是在应用启动时加载的,并且在运行过程中不应该被修改。使用不可变集合来存储配置信息,可以避免在多个线程访问配置数据时出现并发修改的风险。
(二)函数式编程风格支持
在函数式编程中,数据的不可变性是一个重要的原则。不可变集合符合这个原则,使得代码更加符合函数式编程的风格。例如,在对一个集合进行一系列的转换操作(如过滤、映射等)时,使用不可变集合可以保证原始集合不会被修改,并且可以方便地将每个转换操作看作是一个独立的函数,它们的输入是一个集合,输出是一个新的(可能经过修改的)不可变集合。
(三)防止意外修改
在一些复杂的代码库或者大型项目中,可能有多个地方会访问和使用同一个集合。如果这个集合是可变的,那么在某个地方的意外修改可能会导致其他地方出现难以预料的错误。使用不可变集合可以避免这种情况的发生,保证数据的稳定性和可预测性。例如,在一个数据缓存系统中,缓存的部分数据可能是不应该被修改的,使用不可变集合来存储这些数据可以防止其他模块不小心修改了缓存数据,从而保证缓存的一致性。