从 `tmp.set(i, nums[j])` 发现 ArrayList 初始化方式的差异

78 阅读4分钟

引言

在 Java 编程中,ArrayList 是一个非常常用的动态数组实现。然而,在使用它时,我发现了一些让我困惑的现象,尤其是在执行 tmp.set(i, nums[j]) 时,不同的初始化方式竟然导致了完全不同的行为。这让我意识到,我之前对 ArrayListsizecapacity 存在误解。本文将通过我的经历,总结这两种初始化方式的差异,并澄清 sizecapacity 的具体含义,希望能帮助其他开发者避免类似的困惑。

问题的背景

我在编写代码时,尝试用 tmp.set(i, nums[j]) 来设置 ArrayList 中的元素,却发现结果出乎意料。我比较了以下两种初始化方式:

  1. List<Integer> tmp = Arrays.asList(new Integer[nums.length]);
  2. List<Integer> tmp = new ArrayList<>(nums.length);

通过调试,我发现这两种方式在执行 set 操作时的表现截然不同,而原因就藏在 sizecapacity 的差异中。更让我惊讶的是,我之前误以为 new ArrayList<>(16) 创建了一个可以直接通过 set 操作索引 4 的列表,但事实并非如此。

sizecapacity 的定义

ArrayList 中,sizecapacity 是两个核心概念,它们含义不同:

  • size:表示 ArrayList 中当前实际存储的元素数量。可以用 size() 方法获取。
  • capacity:表示 ArrayList 内部数组的容量,也就是在需要扩容之前,数组能容纳的最大元素数量。

初始化时的表现

  • 默认构造new ArrayList<>()

    • size = 0(没有元素)
    • capacity = 10(默认容量)
  • 指定容量构造new ArrayList<>(16)

    • size = 0(仍然没有元素)
    • capacity = 16(内部数组预分配 16 个位置)
  • 使用 Arrays.asListArrays.asList(new Integer[3])

    • size = 3(列表长度等于数组长度)
    • capacity 不适用(Arrays.asList 返回的是固定大小的列表,不是 ArrayList,没有动态扩容的 capacity 概念)

我的误解

我之前以为,new ArrayList<>(16) 创建了一个容量为 16 的列表后,就可以直接用 tmp.set(4, someValue) 设置索引 4 的元素。然而,当我运行代码时,却抛出了 IndexOutOfBoundsException。原因在于,我混淆了 sizecapacity

  • new ArrayList<>(16) 只设置了 capacity 为 16,但 size 仍然是 0。
  • set(int index, E element) 方法要求 index < size(),而不能依赖 capacity
  • 因为 size 是 0,任何 set 操作都会失败。

这让我意识到,capacity 只是预留空间,而 size 才是决定能否使用 set 的关键。

两种初始化方式的具体差异

为了更清楚地说明问题,我以 nums.length = 3 为例,分析两种初始化方式在执行 tmp.set(i, nums[j]) 时的行为:

1. List<Integer> tmp = Arrays.asList(new Integer[3]);

  • 初始状态

    • size = 3
    • 元素为 [null, null, null]
    • 这是一个固定大小的列表,不能扩容。
  • set 操作

    • 可以执行 tmp.set(0, 10)tmp.set(1, 20)tmp.set(2, 30),因为 size 是 3,索引 0 到 2 都有效。
    • 不能执行 tmp.set(3, 40),会抛出异常,因为索引超出了 size
  • 限制

    • 不能用 add 添加新元素,因为列表大小固定。

2. List<Integer> tmp = new ArrayList<>(3);

  • 初始状态

    • size = 0
    • capacity = 3
    • 内部数组为空。
  • set 操作

    • 不能执行 tmp.set(0, 10),因为 size 是 0,没有任何元素可供修改,会抛出 IndexOutOfBoundsException
  • 解决方法

    • 需要先用 add 添加元素,例如:
      tmp.add(0); // size = 1
      tmp.add(0); // size = 2
      tmp.add(0); // size = 3
      tmp.set(0, 10); // 现在可以修改
      
    • 添加超过 3 个元素时,capacity 会自动扩展(通常翻倍)。

需求总结:sizecapacity 的差异

通过这次经历,我总结了在使用 ArrayList 时,如何根据需求选择初始化方式,以及 sizecapacity 的具体差异:

属性Arrays.asList(new Integer[n])new ArrayList<>(n)
size初始为 n,等于数组长度初始为 0,需要通过 add 增加
capacity不适用(固定大小,无扩容机制)初始为 n,可动态扩容
set 操作可直接对 0 到 n-1 的索引操作只能对 0 到 size-1 的索引操作
动态性固定大小,不能 addremove动态大小,支持 addremove

适用场景

  • 需要固定大小且初始元素为 null

    • 使用 Arrays.asList(new Integer[nums.length])
    • 适合直接用 set 修改元素,但不能扩展。
  • 需要动态调整大小

    • 使用 new ArrayList<>(initialCapacity)
    • 适合通过 add 逐步填充数据,capacity 可根据需要扩容。

注意事项

  • set 的限制:只能修改已有元素(index < size),不能用来添加新元素。
  • add 的作用:增加 size,并在必要时扩展 capacity
  • 误解澄清new ArrayList<>(16) 并不意味着可以直接 set 索引 0 到 15 的元素,必须先通过 addsize 增长。

结论

通过分析 tmp.set(i, nums[j]) 的行为,我终于厘清了 ArrayListsizecapacity 的区别。size 是当前元素数量,直接影响 set 操作的有效范围;capacity 是内部数组的容量,影响性能而非功能的可用性。