java 中 hashset 本身是可变的,要实现不可变集合需调用 collections.unmodifiableset() 包装现有实例。该方法返回一个视图,对原集合的修改会反映在视图中,因此必须确保原集合不再被修改或已冻结数据。此方法不会深拷贝对象,仅阻止通过视图进行结构变更。

如何正确创建不可变 HashSet
使用 Collections.unmodifiableSet() 包装新创建的 HashSet 即可得到不可变视图。关键步骤是先构建完整数据再包装,避免中途暴露可变引用。示例代码展示了从空集到含元素集的两种创建方式,均通过 try-catch 验证了 UnsupportedOperationException 的抛出行为。

import java.util.*;
public class ImmutableHashSetExample {
public static void main(String[] args) {
// 方式一:先创建后包装
Set<string> mutableSet = new HashSet();
mutableSet.add("apple");
mutableSet.add("banana");
Set<string> immutableSet1 = Collections.unmodifiableSet(mutableSet);
// 方式二:使用匿名内部类初始化(双括号初始化)
Set<string> immutableSet2 = Collections.unmodifiableSet(new HashSet() {{
add("cherry");
add("date");
}});
// 验证不可变性
try {
immutableSet1.add("grape");
} catch (UnsupportedOperationException e) {
System.out.println("添加操作被拒绝: " + e.getClass().getSimpleName());
}
try {
immutableSet2.remove("cherry");
} catch (UnsupportedOperationException e) {
System.out.println("移除操作被拒绝: " + e.getClass().getSimpleName());
}
// 验证原始集合仍可修改(危险!)
mutableSet.add("elderberry");
System.out.println("immutableSet1 包含 elderberry? " + immutableSet1.contains("elderberry"));
}
}</string></string></string>
运行结果:
添加操作被拒绝: UnsupportedOperationException
移除操作被拒绝: UnsupportedOperationException
immutableSet1 包含 elderberry? true
为什么直接返回 HashSet 会导致可变性泄露
若直接将 new HashSet 赋值给 Set 接口变量但未包装,外部仍可通过该引用修改集合。即使声明为 final,仅防止重新赋值,不阻止内容变更。必须通过 unmodifiableSet 包装才能提供真正的写入保护。以下代码演示了未包装时的风险及包装后的防护效果。
掌握 ES6+ 特性,包括 async/await、解构、扩展运算符、箭头函数、Promise、模块、迭代器、生成器以及函数式编程模式,编写简洁高效的 Java

import java.util.*;
public class MutableLeakExample {
private Set<string> data;
// 错误做法:直接暴露可变集合
public Set<string> getBadData() {
return data; // 调用者可修改内部状态
}
// 正确做法:返回不可变视图
public Set<string> getGoodData() {
return Collections.unmodifiableSet(data);
}
public MutableLeakExample() {
data = new HashSet();
data.add("internal");
}
public static void main(String[] args) {
MutableLeakExample example = new MutableLeakExample();
// 尝试通过 bad getter 修改
try {
example.getBadData().add("leaked");
System.out.println("bad getter 允许修改");
} catch (UnsupportedOperationException e) {
System.out.println("bad getter 拒绝修改");
}
// 尝试通过 good getter 修改
try {
example.getGoodData().add("protected");
System.out.println("good getter 允许修改");
} catch (UnsupportedOperationException e) {
System.out.println("good getter 拒绝修改");
}
}
}</string></string></string>
运行结果:
bad getter 允许修改
good getter 拒绝修改
当原集合被意外修改时不可变视图会发生什么
由于 Collections.unmodifiableSet() 返回的是动态视图而非副本,任何对底层 HashSet 的直接修改都会立即反映在不可变视图中。这可能导致程序逻辑出现意外行为,因为调用者以为数据是固定的。务必在构造完成后彻底放弃对原集合的引用,或使用私有字段封装。以下代码模拟了这种副作用场景。
import java.util.*;
public class ViewSideEffectExample {
public static void main(String[] args) {
Set<string> original = new HashSet();
original.add("alpha");
Set<string> unmodifiableView = Collections.unmodifiableSet(original);
System.out.println("初始视图大小: " + unmodifiableView.size());
// 模拟其他代码路径意外修改原集合
original.add("beta");
original.add("gamma");
System.out.println("修改后视图大小: " + unmodifiableView.size());
System.out.println("视图内容: " + unmodifiableView);
// 此时 unmodifiableView 已包含 beta 和 gamma
// 尽管它自身不能修改,但内容已变
}
}</string></string>
运行结果:
初始视图大小: 1
修改后视图大小: 3
视图内容: [alpha, beta, gamma]
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










