1. copyOf 方法:List.copyOf、Set.copyOf、Map.copyOf
设想这样的场景:有一个方法需要返回一个列表、集合或映射,但又必须确保调用方无法修改该集合的内容。例如,“受支持的货币列表”或“用户角色集合”。任何偶然的插入/删除都可能破坏业务逻辑。
在 Java 10 之前,要么手动返回集合的副本,要么使用类似 Collections.unmodifiableList(list) 的包装器。包装器只防止通过它本身进行修改,不能防止对原始集合的修改。
现在我们有了更可靠的方法——copyOf 方法。
这是什么?
List.copyOf(Collection)、Set.copyOf(Collection)、Map.copyOf(Map)(Java 10)会创建传入集合或映射的真实不可变副本。
- 副本与原集合无关联:原集合的更改不会影响 copyOf 的结果。
- 无法修改副本:任何修改都会抛出 UnsupportedOperationException。
创建不可变副本的示例
import java.util.*;
public class CopyOfDemo {
public static void main(String[] args) {
List<String> modifiable = new ArrayList<>();
modifiable.add("Java");
modifiable.add("Python");
// 创建不可变副本
List<String> immutable = List.copyOf(modifiable);
// 尝试修改副本
try {
immutable.add("C++"); // 将抛出 UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("无法向不可变集合添加元素!");
}
// 修改原始列表
modifiable.add("Kotlin");
// 副本保持不变!
System.out.println("原始列表: " + modifiable);
System.out.println("不可变副本: " + immutable);
}
}
程序输出:
无法向不可变集合添加元素!
原始列表: [Java, Python, Kotlin]
不可变副本: [Java, Python]
各方法简述
- List.copyOf(Collection) —— 返回包含源集合元素的不可变列表。
- Set.copyOf(Collection) —— 返回不可变集合;重复元素将被去重。
- Map.copyOf(Map) —— 返回包含源映射条目的不可变映射。
Set 和 Map 示例
import java.util.*;
public class CopyOfSetMapDemo {
public static void main(String[] args) {
Set<String> modifiableSet = new HashSet<>(Set.of("A", "B", "C"));
Set<String> immutableSet = Set.copyOf(modifiableSet);
// 尝试添加元素
try {
immutableSet.add("D");
} catch (UnsupportedOperationException e) {
System.out.println("无法修改 Set!");
}
Map<String, Integer> modifiableMap = new HashMap<>();
modifiableMap.put("Alice", 30);
modifiableMap.put("Bob", 25);
Map<String, Integer> immutableMap = Map.copyOf(modifiableMap);
try {
immutableMap.put("Charlie", 28);
} catch (UnsupportedOperationException e) {
System.out.println("无法修改 Map!");
}
}
}
2. copyOf 方法的特性与限制
不允许出现 null 元素
与工厂方法 List.of、Set.of、Map.of 一样,copyOf 禁止将 null 作为元素、键或值。如果源集合/映射中存在 null,拷贝时会抛出 NullPointerException。
List<String> listWithNull = Arrays.asList("A", null, "B");
List<String> immutable = List.copyOf(listWithNull); // 将抛出 NullPointerException!
集合确实是不可变的
任何对 add/remove/put/replace 的调用都会导致抛出 UnsupportedOperationException。
原集合与副本相互独立
对原集合的更改不会影响副本,反之亦然。
如果集合已经不可变——将返回它本身
如果传入的集合已不可变(例如 List.of(...) 的结果),copyOf 会返回同一个对象(节省内存)。
List<String> immutable = List.of("X", "Y");
List<String> copy = List.copyOf(immutable);
System.out.println(immutable == copy); // true
不保证具体实现
返回的集合类型仅是 List、Set 或 Map。它不一定是 ArrayList、HashSet 或 HashMap。不要依赖实现细节(例如内部类型与优化);契约只保证不可变性与相应接口。
3. copyOf 与(不可修改)包装器的区别
Collections.unmodifiableList(list) 会在现有集合之上创建包装器。如果原始集合发生变化,这些变化会在包装器中体现出来。
List.copyOf(list) 则会创建一个新的、与原集合无关的不可变集合。
差异演示
import java.util.*;
public class WrapperVsCopyOf {
public static void main(String[] args) {
List<String> original = new ArrayList<>(List.of("A", "B"));
// 包装器
List<String> wrapper = Collections.unmodifiableList(original);
// 副本
List<String> copy = List.copyOf(original);
// 修改原始集合
original.add("C");
System.out.println("包装器: " + wrapper); // [A, B, C]
System.out.println("副本: " + copy); // [A, B]
}
}
输出:
包装器: [A, B, C]
副本: [A, B]
包装器会反映原集合的变化,而副本保持不变。
4. copyOf 的常见使用场景
在方法返回值中保护数据
public class CurrencyService {
private final List<String> currencies = new ArrayList<>(List.of("USD", "EUR", "JPY"));
public List<String> getSupportedCurrencies() {
// 调用方无法修改返回结果
return List.copyOf(currencies);
}
}
现在调用方无法向返回列表添加或删除货币。
在应用分层之间传递
当数据在各层之间传递(DAO → 服务层 → 控制器)时,使用 copyOf 可以确保:在“传递途中”没人会修改集合。
在多线程程序中进行安全发布
不可变集合是在线程之间安全共享数据的便利方式,无需额外同步。
5. 使用 copyOf 和不可变集合时的常见错误
错误 1:尝试插入 null。 copyOf(以及 of)不接受 null——无论是列表/集合元素,还是映射的键/值。拷贝时会抛出 NullPointerException。
错误 2:将包装器与副本混淆。 copyOf 会创建独立副本:原集合的更改在副本中不可见。而 Collections.unmodifiableList(list) 只是对原集合做了包装:原集合的所有更改都会体现在包装器中。
错误 3:试图修改不可变集合。 对来自 copyOf/of 的集合调用 add/remove/put 会导致抛出 UnsupportedOperationException。
错误 4:忽略了映射工厂方法的限制。 例如,Map.of() 最多支持 10 组键–值对。若需要更多——请使用 Map.ofEntries(...),或先构建可变映射再使用 Map.copyOf。
错误 5:依赖具体实现。 不要指望 copyOf 返回的就是 ArrayList 或 HashSet。只保证接口(List/Set/Map)与不可变性。
GO TO FULL VERSION