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,循环中比较 ilastIndex 即可。此方法同时适用于对象数组和原始数组,简单粗暴且高效。

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 需通过 IterableStream 获取,因此需将数组转换为相应类型。

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 为三种基本类型提供了专用流: IntStreamLongStreamDoubleStream。因此若要迭代 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:利用 IteratorhasNext() 方法,对象数组可直接使用 Stream API
自定义 Iterator:原始数组的优化方案,避免中间转换开销

根据实际场景选择合适方案:若追求性能优先索引循环;若需代码简洁可选 for-each + 计数器;若已有 Stream 上下文可用 Iterator;对原始数组则推荐自定义 Iterator

示例完整源码请查阅 GitHub 仓库


原始标题:Checking if an Element is the Last Element While Iterating Over an Array | Baeldung