1. 概述
在Java中,ArrayList
是常用的 List
实现之一。
本教程将探讨如何反转一个 ArrayList
。
2. 问题介绍
我们通过一个例子来理解这个问题。假设我们有一个 Integer
类型的 List
:
List<Integer> aList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
反转后,预期结果如下:
List<Integer> EXPECTED = new ArrayList<>(Arrays.asList(7, 6, 5, 4, 3, 2, 1));
所以,需求看起来相当直接。然而,问题可能有几种变体:
- 在原地反转
List
- 反转
List
并以新的List
对象返回结果
本教程将涵盖这两种情况。
Java标准库提供了一个辅助方法来完成这项工作。我们将看到如何使用这个方法快速解决问题。
此外,考虑到一些人可能正在学习Java,我们将探讨两种有趣但高效的反转 List
的实现方式。
接下来,让我们动手实践。
3. 使用标准 Collections.reverse
方法
Java标准库提供了 [Collections.reverse](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collections.html#reverse(java.util.List))
方法,用于反转给定 List
中元素的顺序。
这个方便的方法实现了原地反转,它会直接改变接收的 List
的顺序。首先,我们创建一个单元测试方法来理解其工作原理:
List<Integer> aList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
Collections.reverse(aList);
assertThat(aList).isEqualTo(EXPECTED);
当执行上述测试时,它会通过。如我们所见,我们将 aList
对象传递给了 reverse
方法,然后 aList
中元素的顺序就被反转了。
如果我们不想改变原始 List
,并期望得到一个新的包含反转顺序元素的 List
对象,我们可以将一个新的 List
对象传递给 reverse
方法:
List<Integer> originalList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
List<Integer> aNewList = new ArrayList<>(originalList);
Collections.reverse(aNewList);
assertThat(aNewList).isNotEqualTo(originalList).isEqualTo(EXPECTED);
这样,我们保持 originalList
不变,而 aNewList
中元素的顺序被反转。
从上面两个示例中,我们可以看出标准的 Collections.reverse
方法非常适合反转 List
。
然而,如果我们在学习Java,可能希望亲自练习实现一个“reverse”方法。接下来,我们将探索两种不错的实现:一种使用递归,另一种使用简单的循环。
4. 递归方式反转 List
首先,让我们使用递归技术实现自己的列表反转方法。先来看看实现:
public static <T> void reverseWithRecursion(List<T> list) {
if (list.size() > 1) {
T value = list.remove(0);
reverseWithRecursion(list);
list.add(value);
}
}
如我们所见,上述实现看起来相当紧凑。现在,让我们理解它是如何工作的。
递归逻辑的停止条件是 list.size() <= 1
。换句话说,如果 list
对象为空或仅包含一个元素,我们就停止递归。
在每次递归调用中,我们执行“T value = list.remove(0)
”,从列表中弹出第一个元素。它的工作原理如下:
recursion step 0: value = null, list = (1, 2, 3, ... 7)
|_ recursion step 1: value = 1, list = (2, 3, 4,...7)
|_ recursion step 2: value = 2, list = (3, 4, 5, 6, 7)
|_ recursion step 3: value = 3, list = (4, 5, 6, 7)
|_ ...
|_ recursion step 6: value = 6, list = (7)
当我们看到 list
对象仅包含一个元素(7)时,我们停止递归,并从底部开始执行 list.add(value)
。也就是说,我们首先将 6 添加到列表的末尾,然后是 5,然后是 4,以此类推。最终,列表中元素的顺序原地反转。此外,这个方法的时间复杂度为线性。
接下来,我们创建一个测试来验证我们的递归实现是否按预期工作:
List<Integer> aList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
ReverseArrayList.reverseWithRecursion(aList);
assertThat(aList).isEqualTo(EXPECTED);
如果运行测试,它会通过。所以,我们的递归实现解决了问题。
5. 循环方式反转 List
我们刚刚使用递归反转了列表。另外,我们也可以使用迭代来解决这个问题。
首先,让我们看看实现:
public static <T> void reverseWithLoop(List<T> list) {
for (int i = 0, j = list.size() - 1; i < j; i++) {
list.add(i, list.remove(j));
}
}
正如我们所见,迭代实现也很整洁。然而,我们只有一个 for
循环,循环体中只有一个语句。
接下来,让我们理解它是如何工作的。
我们定义了两个指针 i
和 j
,它们都指向给定列表。指针 j
永远指向列表的最后一个元素,而指针 i
从 0 递增到 j-1
。
在每次迭代步骤中,我们移除最后一个元素并将它添加到 i
位置,即 list.add(i, list.remove(j))
。当 i
达到 j-1
时,循环结束,列表就被反转了:
Iteration step 0: i = j = null, list = (1, 2, 3,...7)
Iteration step 1: i = 0; j = 6
|_ list.add(0, list.remove(6))
|_ list = (7, 1, 2, 3, 4, 5, 6)
Iteration step 2: i = 1; j = 6
|_ list.add(1, list.remove(6))
|_ list = (7, 6, 1, 2, 3, 4, 5)
...
Iteration step 5: i = 4; j = 6
|_ list.add(4, list.remove(6))
|_ list = (7, 6, 5, 4, 3, 1, 2)
Iteration step 6: i = 5; j = 6
|_ list.add(5, list.remove(6))
|_ list = (7, 6, 5, 4, 3, 2, 1)
这种方法的时间复杂度也是线性的。
最后,让我们测试我们的方法,看看它是否按预期工作:
List<Integer> aList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
ReverseArrayList.reverseWithLoop(aList);
assertThat(aList).isEqualTo(EXPECTED);
当运行上述测试时,它会通过。
6. 总结
在这篇文章中,我们通过示例探讨了如何反转 ArrayList
。标准的 Collections.reverse
方法非常方便地解决了这个问题。
然而,如果我们想创建自己的反转实现,我们已经学习了两种有效的原地反转方法。
如往常一样,本文的完整代码可以在 GitHub 上找到。