1. 概述

本文将系统梳理在 Java 中复制 List 的多种方式,并重点指出一个常见的“踩坑”点:看似复制成功,实则共享引用,导致修改一处影响多处

如果你对 Java 集合框架还不熟悉,建议先阅读 Java 集合基础

2. 使用构造函数复制

最简单直接的方式是使用 ArrayList 的构造函数,传入原始 List:

List<Plant> copy = new ArrayList<>(list);

优点:代码简洁,性能较好。
⚠️ 关键点:这种方式复制的是对象引用,而不是对象本身。也就是说,两个 List 中的元素指向的是堆中同一个对象。

这意味着:
❌ 如果你修改 copy 中某个 Plant 对象的属性,list 中对应对象也会被修改,因为它们是同一个实例。

因此,这种方式只推荐用于不可变对象(immutable objects),比如:

List<Integer> copy = new ArrayList<>(list);

Integer 是不可变类,一旦创建其值就不能更改。多个 List 共享同一个 Integer 引用是安全的,因为没人能改变它的值。

3. 避免 ConcurrentModificationException

⚠️ 常见问题:在多线程环境下,一边遍历(或复制)List,一边修改它,极易触发 ConcurrentModificationException

这通常发生在:一个线程正在复制 List,另一个线程同时在修改原 List。

解决方案:

使用线程安全的集合类
例如 CopyOnWriteArrayList,它在每次修改时都会创建底层数组的新副本,读操作无需加锁,非常适合读多写少的场景。
详情参考:Java CopyOnWriteArrayList 详解

显式加锁
使用 ReentrantReadWriteLock 等同步工具,在复制期间对原集合加锁,保证读取时的一致性。

避免复制
如果可能,设计上避免在并发场景下频繁复制可变集合,考虑使用不可变集合或发布后不再修改的数据结构。

4. 使用 addAll 方法

另一种常见方式是先创建空 List,再调用 addAll

List<Integer> copy = new ArrayList<>();
copy.addAll(list);

⚠️ 注意:和构造函数一样,addAll 也是复制引用,不是深拷贝。
两个 List 仍然共享相同的对象引用,修改对象属性会相互影响。

5. 使用 Collections.copy()

Collections 工具类提供了一个 copy() 静态方法,用于将源 List 的元素复制到目标 List。

📌 关键限制:目标 List 的大小必须大于等于源 List,否则会抛出 IndexOutOfBoundsException

示例 1:目标 List 大小等于源 List

List<Integer> source = Arrays.asList(1, 2, 3);
List<Integer> dest = new ArrayList<>(Arrays.asList(4, 5, 6));
Collections.copy(dest, source);
// 结果:dest = [1, 2, 3]

所有元素被覆盖。

示例 2:目标 List 更大

List<Integer> source = Arrays.asList(1, 2, 3);
List<Integer> dest = new ArrayList<>(Arrays.asList(5, 6, 7, 8, 9, 10));
Collections.copy(dest, source);
// 结果:dest = [1, 2, 3, 8, 9, 10]

只有前三个元素被替换,后面的元素保留。

📌 注意dest 必须是可变的 ArrayList,不能是 Arrays.asList() 返回的固定大小列表,否则会抛出 UnsupportedOperationException

6. 使用 Java 8 Stream

Java 8 的 Stream API 提供了更灵活的复制方式,尤其适合需要过滤或转换的场景。

基本复制

List<String> copy = list.stream()
    .collect(Collectors.toList());

带跳过(skip)

List<String> copy = list.stream()
    .skip(1) // 跳过第一个元素
    .collect(Collectors.toList());

带过滤(filter)

按字符串长度过滤:

List<String> copy = list.stream()
    .filter(s -> s.length() > 10)
    .collect(Collectors.toList());

按对象属性过滤:

List<Flower> flowers = list.stream()
    .filter(f -> f.getPetals() > 6)
    .collect(Collectors.toList());

空安全处理(Null-Safe)

为避免 listnull 导致 NullPointerException,可以这样写:

List<Flower> flowers = Optional.ofNullable(list)
    .map(List::stream)
    .orElseGet(Stream::empty)
    .collect(Collectors.toList());

推荐:这种写法简洁且安全,是处理可能为空集合的标准模式。

也可以组合使用,比如跳过第一个元素且空安全:

List<Flower> flowers = Optional.ofNullable(list)
    .map(List::stream)
    .orElseGet(Stream::empty)
    .skip(1)
    .collect(Collectors.toList());

7. 使用 Java 10 List.copyOf()

Java 10 引入了 List.copyOf(),可以创建一个不可变的 List 副本:

List<T> copy = List.copyOf(list);

优点

  • 语法极其简洁。
  • 返回的 List 是不可变的(immutable),防止意外修改。
  • 如果传入的 List 本身不可变,copyOf() 可能直接返回原引用,性能更优。

⚠️ 注意:返回的 List 不支持 addremove 等修改操作,调用会抛出 UnsupportedOperationException

8. 总结

方法 是否复制引用 是否支持过滤 是否线程安全 推荐场景
new ArrayList<>(list) ✅ 是 ❌ 否 ❌ 否 简单复制,尤其不可变对象
addAll() ✅ 是 ❌ 否 ❌ 否 同上,语义更明确
Collections.copy() ✅ 是 ❌ 否 ❌ 否 需保持索引位置,目标 List 已存在
Stream.collect() ✅ 是 ✅ 是 ❌ 否 需要过滤、跳过、转换等操作
List.copyOf() (Java 10+) ✅ 是 ❌ 否 ✅ 是(不可变) 创建不可变副本,简洁安全

📌 核心要点

  • 所有上述方法都是浅拷贝(shallow copy),只复制引用。
  • 如需深拷贝(deep copy),需手动实现或使用序列化等机制。
  • 多线程环境下注意 ConcurrentModificationException,优先考虑线程安全集合或不可变集合。
  • Java 10 的 List.copyOf() 是创建不可变副本的首选方式,简单粗暴且安全。

原始标题:Copy a List to Another List in Java | Baeldung