collections.swap通过直接操作底层引用实现元素交换,不改变list大小:arraylist为o(1)三次数组赋值,linkedlist为o(min距离)遍历后交换item字段,均抛indexoutofboundsexception而非arrayindexoutofboundsexception,且对不可修改list抛unsupportedoperationexception。

为什么 Collections.swap 不修改 List 大小却能交换元素
它只是直接操作底层容器的引用位置,不涉及扩容、移位或新建结构。对 ArrayList 来说,本质就是三次数组赋值;对 LinkedList,则是遍历到两个节点后交换它们的 item 字段(不是重连指针)。所以它快、安全、无副作用。
- 只适用于支持随机访问或可遍历的
List实现,比如ArrayList、LinkedList、CopyOnWriteArrayList - 传入下标越界会抛
IndexOutOfBoundsException,不是ArrayIndexOutOfBoundsException—— 注意这个异常类型差异 - 如果 List 是
unmodifiable或immutable(如Arrays.asList()返回的、List.of()创建的),调用会立即抛UnsupportedOperationException
Collections.swap 在 ArrayList 和 LinkedList 中的行为差异
表面上看都是“交换两个索引处的元素”,但底层逻辑完全不同:前者靠下标直取,后者必须从头或尾开始找节点。这意味着时间复杂度不同,也影响你是否该用它。
-
ArrayList:O(1),两次get+ 一次set,实际是三行数组操作:Object tmp = list[i]; list[i] = list[j]; list[j] = tmp; -
LinkedList:O(min(i, j, size-i, size-j)),它会选离得近的一端开始遍历,找到两个节点后再交换node.item—— 并不改next/prev指针 - 如果你频繁在
LinkedList中按索引 swap,性能可能比预期差很多,尤其是靠近尾部时
自己手写 swap 时最容易漏掉的边界检查
官方 Collections.swap 会校验 i == j、负数下标、越界,但很多人自己写就只做 tmp = a[i]; a[i] = a[j]; a[j] = tmp;,结果在线上出问题。
- 必须先检查
i >= 0 && j >= 0,否则ArrayList的elementData[i]会触发ArrayIndexOutOfBoundsException,而标准异常应是IndexOutOfBoundsException - 必须检查
i ,不能只用 <code>size - 1做上限,因为size可能为 0 - 允许
i == j,此时什么也不做 —— 这是规范行为,不是 bug
什么情况下不该用 Collections.swap
它很轻量,但不是万能的。有些场景看似适合,实则藏着隐性成本或语义错误。
- 要交换的是子列表(
subList)里的元素?不行 ——subList返回的是视图,swap会作用于原列表,但下标需按原列表算,极易错位 - List 被多个线程并发读写?
Collections.swap本身不加锁,也不是原子操作(三步赋值),需要外层同步 - 元素是 null 敏感的(比如后续有
Objects.requireNonNull)?swap 本身不判空,但若原来某位置是 null,交换后 null 就跑到别处去了,容易引发 NPE 且难定位
真正要注意的,是它不改变结构、不触发回调、不通知观察者 —— 所有基于 List 结构变更的监听机制(比如 PropertyChangeListener 或某些 UI 绑定)对 swap 完全无感。










