跳到主要内容Java 不可变集合详解:List、Set 与 Map 的正确使用 | 极客日志Javajava
Java 不可变集合详解:List、Set 与 Map 的正确使用
本文详细讲解了 Java 中不可变集合的概念、特性及使用场景。介绍了 List、Set 和 Map 三种不可变集合的创建方式,包括 List.of、Set.of、Map.of 以及 Map.copyOf 等方法。文章通过具体代码示例展示了如何创建不可变集合,并说明了修改操作会抛出 UnsupportedOperationException 异常。此外,还补充了关于参数限制、null 值处理、线程安全性及低版本兼容性的注意事项,帮助开发者正确使用不可变集合以提升代码质量。
CoderByte1 浏览 Java 不可变集合详解:List、Set 与 Map 的正确使用
1. 什么是不可变集合
在 Java 开发中,不可变集合(Immutable Collection)是指长度固定且内容无法修改的集合对象。一旦创建完成,任何尝试添加、删除或修改元素的操作都会抛出 UnsupportedOperationException 异常。
1.1 核心特性
- 长度不可变:创建后容量固定,不能扩容或缩容。
- 内容不可变:元素引用不可更改,通常用于存储常量或配置信息。
- 线程安全:由于状态不可变,多线程环境下无需同步锁即可安全共享。
1.2 使用场景
将数据防御性地拷贝到不可变集合中是一种良好的编程实践,特别是在以下场景中:
- 数据保护:当集合对象被不可信的库调用时,不可变形式可以防止外部代码意外修改内部状态。
- 配置管理:系统启动时的配置参数通常不应在运行时被修改。
- 性能优化:不可变对象可以作为 Hash 表的键(Key),因为其哈希值稳定。
- API 设计:返回不可变集合可以确保调用者无法破坏封装性。
简单理解:不想让别人修改集合中的内容,或者希望保证数据的一致性。
- 斗地主的 54 张牌,是不能添加、不能删除、不能修改的。
- 斗地主的打牌规则(单张、对子、三张等),也是不能修改的。
- 用代码获取的操作系统硬件信息,也是不能被修改的。
2. 不可变集合分类
Java 提供了多种创建不可变集合的方式,主要分为三类:
- 不可变的 List 集合:通过
List.of() 或 Collections.unmodifiableList() 实现。
- 不可变的 Set 集合:通过
Set.of() 实现。
- 不可变的 Map 集合:通过
Map.of()、Map.ofEntries() 或 Map.copyOf() 实现。
3. 不可变的 List 集合
从 Java 9 开始,List 接口提供了静态工厂方法 of(),可以直接创建不可变列表。
3.1 基本用法
import java.util.List;
import java.util.Iterator;
public class ImmutableListDemo {
public static void main(String[] args) {
List<String> list = List.of("张三", "李四", "王五", "赵六");
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
System.out.println(list.get(3));
System.out.println("---------------------------");
for (String s : list) {
System.out.println(s);
}
System.out.println("---------------------------");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------------");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------------------");
try {
list.set(0, "aaa");
} catch (UnsupportedOperationException e) {
System.out.println("修改失败:" + e.getMessage());
}
}
}
3.2 注意事项
- 空集合:
List.of() 可以接受任意数量的参数,包括零个,此时返回一个空列表。
- Null 值:
List.of() 不允许包含 null 元素,否则会抛出 NullPointerException。
- 重复元素:允许重复元素,这与
Set 不同。
4. 不可变的 Set 集合
Set 接口的 of() 方法同样用于创建不可变集合,但增加了唯一性约束。
4.1 基本用法
import java.util.Set;
import java.util.Iterator;
public class ImmutableSetDemo {
public static void main(String[] args) {
Set<String> set = Set.of("张三", "李四", "王五", "赵六");
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------");
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------");
try {
set.add("新成员");
} catch (UnsupportedOperationException e) {
System.out.println("添加失败:" + e.getMessage());
}
}
}
4.2 注意事项
- 唯一性:创建时若存在重复元素,立即抛出
IllegalArgumentException。
- Null 值:同样不允许包含
null 元素。
- 顺序:
Set.of() 返回的集合不保证特定顺序(除非是 LinkedHashSet 相关逻辑,但标准 API 返回的是普通 Set)。
5. 不可变的 Map 集合
Map 的不可变实现稍微复杂一些,因为键值对数量较多时,构造方法参数有限制。
5.1 键值对个数小于等于 10
对于少量键值对,可以使用 Map.of() 方法。
5.1.1 代码示例
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
public class ImmutableMapSmallDemo {
public static void main(String[] args) {
Map<String, String> map = Map.of(
"张三", "南京",
"李四", "北京",
"王五", "上海",
"赵六", "广州",
"孙七", "深圳",
"周八", "杭州",
"吴九", "宁波",
"郑十", "苏州",
"刘一", "无锡",
"陈二", "嘉兴"
);
Set<String> keys = map.keySet();
for (String key : keys) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
Set<Entry<String, String>> entries = map.entrySet();
for (Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
try {
map.put("测试", "测试值");
} catch (UnsupportedOperationException e) {
System.out.println("修改失败:" + e.getMessage());
}
}
}
5.1.2 限制说明
- 参数限制:
Map.of() 最多支持 10 个键值对(即 20 个参数)。超过此数量需编译报错。
- 键唯一性:键必须唯一,否则抛出
IllegalArgumentException。
- Null 限制:键和值均不能为
null。
5.2 键值对个数大于 10
当需要创建包含超过 10 个键值对的不可变 Map 时,需要先构建可变 Map,然后转换为不可变副本。
5.2.1 代码示例
import java.util.HashMap;
import java.util.Map;
public class ImmutableMapLargeDemo {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "南京");
hm.put("李四", "北京");
hm.put("王五", "上海");
hm.put("赵六", "北京");
hm.put("孙七", "深圳");
hm.put("周八", "杭州");
hm.put("吴九", "宁波");
hm.put("郑十", "苏州");
hm.put("刘一", "无锡");
hm.put("陈二", "嘉兴");
hm.put("aaa", "111");
hm.put("bbb", "222");
hm.put("ccc", "333");
Map<String, String> map = Map.copyOf(hm);
try {
map.put("ddd", "444");
} catch (UnsupportedOperationException e) {
System.out.println("修改失败:" + e.getMessage());
}
System.out.println("不可变 Map 大小:" + map.size());
}
}
5.2.2 替代方案对比
除了 Map.copyOf(),也可以使用 Map.ofEntries() 配合 entrySet().toArray(),但 copyOf() 更简洁高效。
6. 最佳实践与总结
6.1 何时使用不可变集合
- 公共 API 返回值:防止调用者修改内部状态。
- 配置常量:如系统路径、默认参数等。
- 多线程共享数据:避免竞态条件,无需加锁。
6.2 常见陷阱
- 空指针异常:
of() 系列方法不支持 null,务必检查数据源。
- 性能开销:每次调用
of() 都会创建新对象,频繁创建可能影响性能,建议复用实例。
- 版本兼容性:
List/Set/Map.of() 仅在 Java 9+ 可用,低版本需使用 Collections.unmodifiableXXX()。
6.3 低版本兼容写法
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
List<String> mutableList = new ArrayList<>();
mutableList.add("A");
mutableList.add("B");
List<String> immutableList = Collections.unmodifiableList(mutableList);
7. 结语
掌握 Java 不可变集合的使用,能够显著提升代码的安全性和健壮性。在实际开发中,应优先选择 of() 系列方法创建轻量级不可变集合,并在必要时使用 copyOf() 处理大数据量场景。同时,注意区分可变与不可变集合的边界,避免因误操作导致程序崩溃。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online