1. 概述
数组操作是编程中的基础技能,无论在什么应用中都可能用到。虽然这些操作常被封装在更方便的接口(如 Collections API)后,但作为开发者,我们仍需尽早掌握这些基础知识。
本文将探讨如何在数组中查找最小元素的索引。我们将讨论适用于不同元素类型的方法,但为简化说明,主要使用整数数组作为示例。
2. 简单迭代法
最简单的解决方案往往是最可靠的——它更容易实现、修改和理解。下面展示如何用基本的 for
循环实现:
@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingForLoop_thenGetCorrectResult(int[] array, int expectedIndex) {
int minValue = Integer.MAX_VALUE;
int minIndex = -1;
for (int i = 0; i < array.length; i++) {
if (array[i] < minValue) {
minValue = array[i];
minIndex = i;
}
}
assertThat(minIndex).isEqualTo(expectedIndex);
}
这段代码虽然略显冗长,但目标明确且健壮:优先解决问题而非追求代码行数最少。对新手友好的同时,也不依赖高级 Java API。
✅ 关键点:循环体中同时跟踪最小值及其索引,避免重复遍历
循环类型可替换:while
或 do-while
同样适用。处理引用类型时,需确保对象实现了 Comparable
,并用 compareTo()
替代 <
运算符。
3. 两步分离法
将任务拆分为两步:先找最小元素,再定位其索引。虽然性能略低于单次遍历,但时间复杂度仍为 O(n):
@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingForLoopAndLookForIndex_thenGetCorrectResult(int[] array, int expectedIndex) {
int minValue = Integer.MAX_VALUE;
for (int number : array) {
if (number < minValue) {
minValue = number;
}
}
int minIndex = -1;
for (int i = 0; i < array.length; i++) {
if (array[i] == minValue) {
minIndex = i;
break;
}
}
assertThat(minIndex).isEqualTo(expectedIndex);
}
⚠️ 注意:第二个循环使用
break
提前退出,但整体性能并未优于单次遍历方案
4. 原生流处理
用 Stream API 消除第一个循环。这里使用 IntStream
处理基本类型数组:
@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingIntStreamAndLookForIndex_thenGetCorrectResult(int[] array, int expectedIndex) {
int minValue = Arrays.stream(array).min().orElse(Integer.MAX_VALUE);
int minIndex = -1;
for (int i = 0; i < array.length; i++) {
if (array[i] == minValue) {
minIndex = i;
break;
}
}
assertThat(minIndex).isEqualTo(expectedIndex);
}
IntStream
提供了原生值序列的便捷操作,将命令式循环转为声明式流。进一步优化第二个循环:
@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingIntStreamAndLookForIndexWithIntStream_thenGetCorrectResult(int[] array, int expectedIndex) {
int minValue = Arrays.stream(array).min().orElse(Integer.MAX_VALUE);
int minIndex = IntStream.range(0, array.length)
.filter(index -> array[index] == minValue)
.findFirst().orElse(-1);
assertThat(minIndex).isEqualTo(expectedIndex);
}
❌ 坑:虽然声明式风格更优雅,但可读性显著下降,尤其对 Stream 经验不足的开发者
依赖库方案:用 Apache Commons 的 ArrayUtils
提升可读性:
@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingIntStreamAndLookForIndexWithArrayUtils_thenGetCorrectResult(int[] array, int expectedIndex) {
int minValue = Arrays.stream(array).min().orElse(Integer.MAX_VALUE);
int minIndex = ArrayUtils.indexOf(array, minValue);
assertThat(minIndex).isEqualTo(expectedIndex);
}
或转换为 List
利用其 indexOf()
方法:
@ParameterizedTest
@MethodSource("referenceTypesProvider")
void givenArray_whenUsingReduceAndList_thenGetCorrectResult(Integer[] array, int expectedIndex) {
List<Integer> list = Arrays.asList(array);
int minValue = list.stream().reduce(Integer.MAX_VALUE, Integer::min);
int index = list.indexOf(minValue);
assertThat(index).isEqualTo(expectedIndex);
}
⚠️ 空间复杂度警告:数组转 List 会将空间复杂度从 O(1) 提升至 O(n),不推荐使用
5. 引用类型数组处理
原生流不适用于引用类型,改用 reduce()
方法:
@ParameterizedTest
@MethodSource("referenceTypesProvider")
void givenArray_whenUsingReduce_thenGetCorrectResult(Integer[] array, int expectedIndex) {
int minValue = Arrays.stream(array).reduce(Integer.MAX_VALUE, Integer::min);
int minIndex = ArrayUtils.indexOf(array, minValue);
assertThat(minIndex).isEqualTo(expectedIndex);
}
reduce()
接收初始值(Integer.MAX_VALUE
)和 min()
方法引用。此处非常规用法:实际是过滤而非聚合。可搭配 ArrayUtils
或 filter()
使用。
6. 流中直接处理索引
在 Stream 中直接操作索引,将逻辑封装在 reduce()
内:
@ParameterizedTest
@MethodSource("primitiveProvider")
void givenArray_whenUsingReduceWithRange_thenGetCorrectResult(int[] array, int expectedIndex) {
int index = IntStream.range(0, array.length)
.reduce((a, b) -> array[a] <= array[b] ? a : b)
.orElse(-1);
assertThat(index).isEqualTo(expectedIndex);
}
❌ 踩坑:此方案可读性极差,要求开发者对 Stream API 有深度理解
7. 总结
数组是 Java 最基础的数据结构。虽然日常开发中可能不直接操作数组,但熟练掌握其操作仍是必备技能。
简单直接的方法通常最优:它们明确易懂且易于维护。使用 Stream API 需要函数式编程基础,可能提升或降低代码可读性,需谨慎使用。
✅ 最佳实践:优先选择简单迭代法,仅在团队熟悉 Stream 且代码可读性可控时使用流式方案
本文所有代码示例可在 GitHub 获取。