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,除非你真的需要精确控制目标容器。

示例代码仓库地址(供参考):



原始标题:Java IndexOutOfBoundsException “Source Does Not Fit in Dest”