CodeGym /课程 /JAVA 25 SELF /Map.copyOf、Set.copyOf 及其他实用工具

Map.copyOf、Set.copyOf 及其他实用工具

JAVA 25 SELF
第 34 级 , 课程 1
可用

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.ofSet.ofMap.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

不保证具体实现

返回的集合类型仅是 ListSetMap。它不一定是 ArrayListHashSetHashMap。不要依赖实现细节(例如内部类型与优化);契约只保证不可变性与相应接口。

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 返回的就是 ArrayListHashSet。只保证接口(List/Set/Map)与不可变性。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION