# 问题

34. 为什么 ArrayListsubList() 不能安全修改?

# 标准答案

在 Java 中,ArrayListsubList() 方法返回的是原列表的一部分视图,而不是一个新的独立副本。对 subList() 返回的视图进行修改会影响原列表,并且可能会导致不一致或并发问题,特别是在多线程环境中。这就是为什么 subList() 的修改操作并不是完全安全的。

# 1. subList()的工作原理

ArrayListsubList() 方法返回的是原始列表的一个视图,而不是一个新的列表副本。这意味着 subList() 返回的子列表和原始列表共享相同的数据存储区域,任何对 subList() 视图的修改都会直接影响原始 ArrayList。不过,subList() 返回的视图有时会造成潜在的并发修改问题。

示例代码:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4);
subList.set(0, 10);  // 修改 subList 中的元素
System.out.println(list);  // 输出 [1, 10, 3, 4, 5]
1
2
3
4

在上面的例子中,subList() 返回的是 list 的一个视图,对 subList 的修改会影响到 list

# 2. 为什么不能安全修改 subList() 返回的列表

subList() 的返回值并不是一个独立的列表,而是对原 ArrayList 中一部分的视图。因此,对 subList() 的修改会直接影响到原列表。当原列表发生结构性修改时,比如元素的添加或删除,subList() 会变得不可预测,甚至可能抛出 ConcurrentModificationException 异常。

以下是几种常见的导致 subList() 不安全修改的场景:

  • 修改原列表的结构:如果你在 subList() 返回的视图上进行修改时,同时对原始列表做出结构性修改(例如,删除或添加元素),这会导致 subList() 视图不一致,从而抛出异常。
  • 并发修改:如果多个线程同时修改原列表和它的 subList(),可能会导致并发问题,因为 subList() 与原列表共享数据区域,没有同步机制来保证线程安全。

# 3. 具体问题和示例

  • 删除原列表中的元素:如果在 subList() 返回的视图修改后,删除原列表中的元素,subList() 可能会抛出 ConcurrentModificationException 异常。

示例代码:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4);

list.remove(2);  // 从原列表删除元素,导致 subList 不一致
1
2
3
4

在上面的代码中,删除原 ArrayList 中的元素会导致 subList() 的视图变得不一致,可能会抛出异常。

# 4. subList() 的限制

  • 不支持结构修改subList() 允许修改元素的值(如通过 set() 方法),但不允许添加或删除元素。也就是说,不能通过 add()remove() 方法修改 subList() 的结构,进行这种操作会抛出 UnsupportedOperationException

例如:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4);
subList.add(6);  // 抛出 UnsupportedOperationException
1
2
3

# 5. 如何避免不安全的修改

为了避免因对 subList() 的修改导致不一致,建议遵循以下实践:

  • 避免对原列表结构的修改:如果需要修改原列表的结构(添加或删除元素),可以先将 subList() 视图转换为一个新的列表,然后再进行修改。

示例:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = new ArrayList<>(list.subList(1, 4));
subList.add(6);  // 现在对 subList 的修改不再影响原列表
1
2
3

这样,subList() 返回的新 ArrayList 就是原列表的副本,修改其内容不会影响原列表。

  • 使用 subList() 时小心结构性修改:如果需要在 subList() 上进行修改,确保不会影响原列表的结构。

# 6. 总结

  • ArrayListsubList() 返回的是原列表的一部分视图,而不是副本。
  • 修改 subList() 时要小心,如果同时修改原列表的结构,可能导致不一致或抛出 ConcurrentModificationException
  • 不允许通过 subList() 对原列表的结构进行修改,如删除、添加元素。

# 核心原理

subList() 返回的子列表与原列表共享相同的底层数组数据,因此它无法确保对 subList() 的修改在所有情况下都是安全的。特别是,当原列表的结构发生变化时,subList() 会变得不可用或抛出异常。

# 最佳实践

  • 如果需要修改 subList() 中的元素,避免修改原列表的结构。
  • 若需要对 subList() 进行结构性修改,考虑将 subList() 转换为一个独立的 ArrayList

# 深入追问

🔹 subList() 如何与其他集合(如 LinkedList)一起使用? 🔹 为什么 subList() 不支持删除或添加元素?

# 相关面试题

  • subList() 为什么不能安全修改?
  • 如何使用 subList() 高效获取列表的部分视图?
  • subList() 的返回值与原列表之间的关系是什么?