1. 概述
在 Java 中,当我们尝试使用 Collections.copy
方法复制一个 List 时,有时会抛出异常:
IndexOutOfBoundsException: “Source does not fit in dest”。
这篇文章将带你深入理解这个异常的触发原因,并提供几种有效的解决方案。同时,我们也会介绍一些替代 Collections.copy
的方法来实现 List 的拷贝。
2. 复现问题
我们先来看一段使用 Collections.copy
来复制 List 的代码:
static List<Integer> copyList(List<Integer> source) {
List<Integer> destination = new ArrayList<>(source.size());
Collections.copy(destination, source);
return destination;
}
这段代码中,我们创建了一个目标 List,其初始容量等于源 List 的大小。然后尝试调用 Collections.copy
进行复制操作。
接着执行如下代码:
List<Integer> source = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> copy = copyList(source);
结果你猜怎么着?直接抛异常了:
java.lang.IndexOutOfBoundsException: Source does not fit in dest
这让人有点懵,明明容量都一样,为啥还报错?
3. 异常的根本原因
来看一下官方文档对 Collections.copy
的说明:
目标 List 的 size 必须 大于等于 源 List 的 size。如果目标 List 更长,则多余位置的元素不会被修改。
⚠️ 关键点来了:
虽然我们用 new ArrayList<>(source.size())
创建了目标 List,但这只是设置了初始容量(capacity),而不是 size!也就是说,这个 List 实际上还是空的,size 为 0。
所以当 Collections.copy
想往里面写入元素时,发现目标 List 没有任何“合法位置”可以写入,于是就抛出了异常。
✅ 简单总结:
容量 ≠ 大小(size)。你只是预分配了内存,但没有实际元素。
4. 解决方案
4.1. 正确使用 Collections.copy
我们来演示一下正确的用法:
List<Integer> destination = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> source = Arrays.asList(11, 22, 33);
Collections.copy(destination, source);
这次我们使用 Arrays.asList
创建了两个都包含实际元素的 List。此时它们的 size 都是明确的,Collections.copy
就能正常工作。
执行后,目标 List 变成:
[11, 22, 33, 4, 5]
⚠️ 注意:如果你交换了 source 和 destination 的位置,而 source 的 size 小于 destination,也会抛异常。
4.2. 使用 ArrayList
构造器
这是最简单粗暴的方式之一:
List<Integer> source = Arrays.asList(11, 22, 33);
List<Integer> destination = new ArrayList<>(source);
这种方式会创建源 List 的一个浅拷贝。注意:只是拷贝了引用,不是深拷贝。
所以如果你的 List 中放的是不可变对象(比如 Integer
, String
),这种方式完全 OK。但如果放的是可变对象,就要小心副作用了。
4.3. 使用 addAll
方法
这也是一种常见的 List 拷贝方式:
List<Integer> destination = new ArrayList<>();
destination.addAll(source);
✅ 优点:
- 简单直观
- 同样是浅拷贝
- 元素会被追加到目标 List 的末尾
4.4. 使用 Java 8 的 Stream API
Stream 也可以用来实现拷贝:
List<Integer> copy = source.stream()
.collect(Collectors.toList());
这种方式创建的是一个新的 List,内容和源 List 一致。
4.5. Java 10 的 List.copyOf
Java 10 提供了一个更现代的方法:
List<Integer> destination = List.copyOf(sourceList);
⚠️ 注意事项:
- 返回的是 不可变 List
- 源 List 不能为 null
- 源 List 中不能包含 null 元素
5. 总结
这篇文章我们重点讲解了 Collections.copy
抛出 IndexOutOfBoundsException: "Source does not fit in dest"
的原因:目标 List 的 size 不够,而不是容量不够。
同时我们还介绍了多种替代方案:
方法 | 是否浅拷贝 | 是否可变 | 适用场景 |
---|---|---|---|
Collections.copy |
✅ | ✅ | 需要精确控制目标 List |
new ArrayList<>(source) |
✅ | ✅ | 简单快速拷贝 |
addAll |
✅ | ✅ | 需要追加元素 |
Stream.collect |
✅ | ✅ | 函数式风格 |
List.copyOf |
✅ | ❌ | 不可变 List,Java 10+ |
📌 建议:
- 如果只是拷贝基本类型或不可变对象,用构造器或
addAll
最方便。 - 如果需要不可变拷贝,Java 10+ 推荐使用
List.copyOf
。 - 谨慎使用
Collections.copy
,除非你真的需要精确控制目标容器。
示例代码仓库地址(供参考):