1. 概述
在Java 数组操作中,有时我们需要在迭代时判断当前元素是否是数组最后一个元素。
本文将探讨几种实现方式,不同方案各有优劣,可根据具体场景灵活选用。
2. 问题场景
先通过示例理解问题。假设有如下数组:
String[] simpleArray = { "aa", "bb", "cc" };
有些人可能认为判断末尾元素很简单——直接比较当前元素与数组末尾元素(即 array[array.length - 1]
)即可。确实,对于 simpleArray
这种无重复元素的数组,这种方法可行。
但一旦数组包含重复元素,此方法就会失效,例如:
static final String[] MY_ARRAY = { "aa", "bb", "cc", "aa", "bb", "cc"};
现在末尾元素是 "cc"
,但数组中有两个 "cc"
。若仅比较元素值,会导致误判。
我们需要一种稳定可靠的方案来识别迭代中的最后一个元素。本文将针对不同迭代场景提供解决方案。为方便演示,我们统一使用 MY_ARRAY
作为输入,目标是生成以下结果字符串:
static final String EXPECTED_RESULT = "aa->bb->cc->aa->bb->cc[END]";
虽然 Java 有多种字符串拼接方式,但本文重点在于展示如何识别末次迭代。
此外还需注意,Java 数组分为对象数组和原始数组(如 int[]
),解决方案需覆盖这两种类型。
3. 基于索引的循环判断
最简单的方式是使用传统索引式 for
循环。这种方式能直接访问元素索引,通过比较当前索引与末尾索引即可判断:
int lastIndex = MY_ARRAY.length - 1;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < MY_ARRAY.length; i++) {
sb.append(MY_ARRAY[i]);
if (i == lastIndex) {
sb.append("[END]");
} else {
sb.append("->");
}
}
assertEquals(EXPECTED_RESULT, sb.toString());
先计算末尾索引 lastIndex
,循环中比较 i
和 lastIndex
即可。此方法同时适用于对象数组和原始数组,简单粗暴且高效。
4. For-Each 循环配合外部计数器
有时我们更倾向使用for-each 循环,因其代码简洁。但它不直接暴露索引。不过我们可以通过外部计数器追踪位置:
int counter = 0;
StringBuilder sb = new StringBuilder();
for (String element : MY_ARRAY) {
sb.append(element);
if (++counter == MY_ARRAY.length) {
sb.append("[END]");
} else {
sb.append("->");
}
}
assertEquals(EXPECTED_RESULT, sb.toString());
手动维护 counter
变量,每次迭代递增,并与数组长度比较。此方案同样适用于原始数组,但需注意计数器初始化和递增位置。
5. 转换为 Iterable 使用 Iterator
Java 的 Iterator 提供了便捷的迭代方式,其 hasNext() 方法天然适合判断末尾元素。
但 Java 数组未实现 Iterable 接口。我们知道 Iterator 需通过 Iterable 或 Stream 获取,因此需将数组转换为相应类型。
5.1. 对象数组
List 实现了 Iterable 接口。因此可将对象数组转为 List 再获取 Iterator:
Iterator<String> it = Arrays.asList(MY_ARRAY).iterator();
StringBuilder sb = new StringBuilder();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append("[END]");
} else {
sb.append("->");
}
}
assertEquals(EXPECTED_RESULT, sb.toString());
这里使用 Arrays.asList()
将 String[]
转为 List<String>
。
也可用 Stream API 转换:
Iterator<String> it = Arrays.stream(MY_ARRAY).iterator();
StringBuilder sb = new StringBuilder();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append("[END]");
} else {
sb.append("->");
}
}
assertEquals(EXPECTED_RESULT, sb.toString());
通过 Arrays.stream()
获取 Stream<String>
后再获取 Iterator。
5.2. 原始数组
先创建示例 int[]
数组和期望结果:
static final int[] INT_ARRAY = { 1, 2, 3, 1, 2, 3 };
static final String EXPECTED_INT_ARRAY_RESULT = "1->2->3->1->2->3[END]";
Stream API 为三种基本类型提供了专用流: IntStream、LongStream 和 DoubleStream。因此若要迭代 int[]
、long[]
或 double[]
,可直接转换为原始类型流:
Iterator<Integer> it = IntStream.of(INT_ARRAY).iterator();
StringBuilder sb = new StringBuilder();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append("[END]");
} else {
sb.append("->");
}
}
assertEquals(EXPECTED_INT_ARRAY_RESULT, sb.toString());
也可使用 Arrays.stream()
获取原始类型流:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
若原始数组不属于上述三种类型(如 char[]
),可先转为包装类型的 List(如 List<Character>
),再用 Iterator 迭代。
⚠️ 但需注意:将原始数组转为 List 本身就需要遍历数组,导致两次遍历(一次转换,一次处理)。因此仅为使用 Iterator 而转换原始数组并非最优方案。
下面介绍如何为其他原始数组实现 Iterator。
6. 自定义 Iterator 实现
Iterator 在判断末尾元素时非常便捷。对于原始数组,若通过转 List 方式获取 Iterator 会损失性能。**更好的方式是自定义 Iterator,无需创建中间对象即可获得 Iterator 的优势**。
先准备 char[]
数组和期望结果:
static final char[] CHAR_ARRAY = { 'a', 'b', 'c', 'a', 'b', 'c' };
static final String EXPECTED_CHAR_ARRAY_RESULT = "a->b->c->a->b->c[END]";
为 char[]
创建自定义 Iterator:
class CharArrayIterator implements Iterator<Character> {
private final char[] theArray;
private int currentIndex = 0;
public static CharArrayIterator of(char[] array) {
return new CharArrayIterator(array);
}
private CharArrayIterator(char[] array) {
theArray = array;
}
@Override
public boolean hasNext() {
return currentIndex < theArray.length;
}
@Override
public Character next() {
return theArray[currentIndex++];
}
}
CharArrayIterator
实现了 Iterator 接口,内部持有 char[]
引用并用 currentIndex
追踪位置。注意 next() 方法中发生了自动装箱(char -> Character
)。
使用方式如下:
Iterator<Character> it = CharArrayIterator.of(CHAR_ARRAY);
StringBuilder sb = new StringBuilder();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append("->");
} else {
sb.append("[END]");
}
}
assertEquals(EXPECTED_CHAR_ARRAY_RESULT, sb.toString());
**若需为其他原始数组实现 Iterator,需创建类似类**。虽然需要额外编码,但避免了中间转换开销。
7. 总结
本文探讨了在 Java 数组迭代中判断末尾元素的多种方案,覆盖了对象数组和原始数组的各种场景。关键点总结如下:
✅ 索引循环:最基础方案,通用性强,适用于所有数组类型
✅ for-each + 计数器:代码简洁,但需维护外部计数器
✅ 转为 Iterable:利用 Iterator 的 hasNext()
方法,对象数组可直接使用 Stream API
✅ 自定义 Iterator:原始数组的优化方案,避免中间转换开销
根据实际场景选择合适方案:若追求性能优先索引循环;若需代码简洁可选 for-each + 计数器;若已有 Stream 上下文可用 Iterator;对原始数组则推荐自定义 Iterator。
示例完整源码请查阅 GitHub 仓库。