# 问题
34. 为什么 ArrayList
的 subList()
不能安全修改?
# 标准答案
在 Java 中,ArrayList
的 subList()
方法返回的是原列表的一部分视图,而不是一个新的独立副本。对 subList()
返回的视图进行修改会影响原列表,并且可能会导致不一致或并发问题,特别是在多线程环境中。这就是为什么 subList()
的修改操作并不是完全安全的。
# 1. subList()
的工作原理
ArrayList
的 subList()
方法返回的是原始列表的一个视图,而不是一个新的列表副本。这意味着 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]
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 不一致
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
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 的修改不再影响原列表
2
3
这样,subList()
返回的新 ArrayList
就是原列表的副本,修改其内容不会影响原列表。
- 使用
subList()
时小心结构性修改:如果需要在subList()
上进行修改,确保不会影响原列表的结构。
# 6. 总结
ArrayList
的subList()
返回的是原列表的一部分视图,而不是副本。- 修改
subList()
时要小心,如果同时修改原列表的结构,可能导致不一致或抛出ConcurrentModificationException
。 - 不允许通过
subList()
对原列表的结构进行修改,如删除、添加元素。
# 核心原理
subList()
返回的子列表与原列表共享相同的底层数组数据,因此它无法确保对 subList()
的修改在所有情况下都是安全的。特别是,当原列表的结构发生变化时,subList()
会变得不可用或抛出异常。
# 最佳实践
- 如果需要修改
subList()
中的元素,避免修改原列表的结构。 - 若需要对
subList()
进行结构性修改,考虑将subList()
转换为一个独立的ArrayList
。
# 深入追问
🔹 subList()
如何与其他集合(如 LinkedList
)一起使用?
🔹 为什么 subList()
不支持删除或添加元素?
# 相关面试题
subList()
为什么不能安全修改?- 如何使用
subList()
高效获取列表的部分视图? subList()
的返回值与原列表之间的关系是什么?