增强型for语句如何处理数组,如何获取数组的迭代器?

77

以下是代码片段:

int[] arr = {1, 2, 3};
for (int i : arr)
    System.out.println(i);

我有以下问题:

  1. 上述for-each循环如何工作?
  2. 如何在Java中获取数组的迭代器?
  3. 数组是否转换为列表以获取迭代器?

一个更快的版本:https://dev59.com/6nNA5IYBdhLWcg3wVcJx?lq=1 - Ciro Santilli OurBigBook.com
上述关于性能的问题已经关闭。答案在这里:https://dev59.com/WnVC5IYBdhLWcg3wjx1d - lue
1
我没有找到一个明确的答案来回答第一点。for each循环会测试正在迭代的项的类型,如果它是一个数组,则在内部使用普通的for循环吗?还是它会即时创建一个Iterable对象?或者它会转换为List?我还没有找到一个明确的答案。 - Elisabeth
12个回答

55
如果您想要一个数组的Iterator,可以使用其中一种直接实现,而不是将数组包装在List中。例如:

Apache Commons Collections ArrayIterator

或者,如果您想使用泛型,则可以使用以下代码:

com.Ostermiller.util.ArrayIterator

请注意,如果您想要基本类型的Iterator,则无法实现,因为基本类型不能是泛型参数。例如,如果您想要一个Iterator<int>,则必须使用Iterator<Integer>,如果它是由int[]支持的,那么会导致大量的自动装箱和拆箱。

2
同意。或者,如果您想继续使用原始数据类型,请构建自己的ArrayIterator。 - Khaled.K
代码补全不会为 ArrayIt 显示任何建议;假设所有第三方库。 - Mark Jeronimus

50
不,没有转换。JVM在后台使用索引迭代数组。
引用自《Effective Java 2nd Ed.》,第46项:
请注意,即使是对于数组,使用for-each循环也不会有性能损失。实际上,在某些情况下,它可能比普通for循环提供略微的性能优势,因为它仅计算一次数组索引的限制。
因此,您无法为数组获取Iterator(除非当然先将其转换为List)。

2
是的,+1。这可能只是一个普通的“for”循环。@Emil,你不能这样做。 - st0le
如果一个集合是空的,那么Effective Java就是错误的。在这种情况下,它会不必要地创建一个迭代器,从而导致对象泛滥。解决方法是将其包装在一个isEmpty()检查中。这在像Android这样内存受限的设备上尤为重要。 - keyboardr
我们确定它没有为数组创建一个可迭代对象,或者将数组转换为列表吗?换句话说,在内部,Java 是否表示“如果类型是数组,则使用带有隐藏索引的常规 for 循环,否则使用 Iterable。”? - Elisabeth

33

Arrays.asList(arr).iterator();

或编写自己的代码,实现ListIterator接口。


23
Arrays.asList() 方法不能接受一个 int[],只能接受 Object[] 及其子类作为参数。 - ILMTitan
3
这是错误的。它返回一个List<int[]>,因此是一个Iterator<int[]>,而不是一个List<Integer> - njzk2
你把数组转换成列表只是为了提供一个迭代器?! - Khaled.K
1
@KhaledKhnifer 实际上并没有太多的转换。Arrays.asList()使用实际数组并将其包装在一个适配器类中,以使其适应列表接口。因此,除了“不支持基元类型”问题(以及对于在非基元情况下遇到相同问题的人)外,这实际上是一个很好的解决方案。 - Jasper
@Khaled.K,看看Zenexer(https://stackoverflow.com/users/1188377/zenexer)的新答案。 - lue

31

Google的Guava Libraries集合提供了如下功能:

Iterator<String> it = Iterators.forArray(array);

相比于已经似乎被放弃的Apache Collection,应该更喜欢使用Guava。


1
这通常是最好的答案,除非我们处理基本类型数组(例如int[])时,这不再适用:(因为我们无法指定Iterator<int>Iterator<Integer> - chakrit

15

在Java 8中:

Arrays.stream(arr).iterator();

10
public class ArrayIterator<T> implements Iterator<T> {
  private T array[];
  private int pos = 0;

  public ArrayIterator(T anArray[]) {
    array = anArray;
  }

  public boolean hasNext() {
    return pos < array.length;
  }

  public T next() throws NoSuchElementException {
    if (hasNext())
      return array[pos++];
    else
      throw new NoSuchElementException();
  }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}

9
严格来说,你不能获得原始数组的迭代器,因为Iterator.next()只能返回一个对象。但通过自动装箱的魔法,你可以使用Arrays.asList()方法获取迭代器。
Iterator<Integer> it = Arrays.asList(arr).iterator();

上面的答案是错误的,你不能在原始数组上使用 Arrays.asList(),它会返回一个 List<int[]>。而应该使用 GuavaInts.asList()

5

您无法直接获取数组的迭代器。

但是,您可以使用一个由您的数组支持的List,并在此列表上获取迭代器。为此,您的数组必须是Integer数组(而不是int数组):

Integer[] arr={1,2,3};
List<Integer> arrAsList = Arrays.asList(arr);
Iterator<Integer> iter = arrAsList.iterator();

注意:这只是理论。你可以像这样获取一个迭代器,但我不建议你这样做。与“扩展for语法”直接在数组上进行迭代相比,性能并不好。
注意2:使用此方法构建的列表不支持所有方法(因为该列表由具有固定大小的数组支持)。例如,你的迭代器的“remove”方法将导致异常。

我认为这不会起作用。Arrays.asList(arr)将返回类型为int []的List,因为它是原始数组。 - Emil
@Emil:Java的泛型不能用于原始类型。Arrays.asList会隐式地将数组中的值进行装箱处理,从而确保结果真正是一个List<Integer> - Konrad Rudolph
@emil 它能工作是因为Arrays.asList接受可变参数。 - Sean Patrick Floyd
@seanizer:但我在我的IDE中尝试了上面的代码,它显示错误。 - Emil
它在我的 Eclipse 中编译通过了。你把编译器兼容性设置到至少 1.5 了吗? - Sean Patrick Floyd

4

以上的for-each循环是如何工作的?

和许多其他数组特性一样,JSL明确提到了数组并赋予它们神奇的属性。JLS 7 14.14.2

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

如果表达式的类型是Iterable的子类型,则翻译如下:
否则,该表达式一定具有数组类型T[]。[[ MAGIC! ]]
L1 ... Lm是增强型for循环语句之前(可能为空的)标签的序列。
增强型for循环语句等效于以下形式的基本for循环语句:
T[] #a = Expression;
L1: L2: ... Lm:
for (int #i = 0; #i < #a.length; #i++) {
    VariableModifiersopt TargetType Identifier = #a[#i];
    Statement
}

#a#i是自动生成的标识符,与任何其他在加强型for语句出现点的作用域中的标识符(自动生成的或其他)都不同。

数组是否被转换为列表以获取迭代器?

让我们使用javap进行分析:

public class ArrayForLoop {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int i : arr)
            System.out.println(i);
    }
}

那么:

javac ArrayForLoop.java
javap -v ArrayForLoop

main方法进行了一些编辑,使其更易于阅读:

 0: iconst_3
 1: newarray       int
 3: dup
 4: iconst_0
 5: iconst_1
 6: iastore
 7: dup
 8: iconst_1
 9: iconst_2
10: iastore
11: dup
12: iconst_2
13: iconst_3
14: iastore

15: astore_1
16: aload_1
17: astore_2
18: aload_2
19: arraylength
20: istore_3
21: iconst_0
22: istore        4

24: iload         4
26: iload_3
27: if_icmpge     50
30: aload_2
31: iload         4
33: iaload
34: istore        5
36: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
39: iload         5
41: invokevirtual #3    // Method java/io/PrintStream.println:(I)V
44: iinc          4, 1
47: goto          24
50: return

分解:

  • 014: 创建数组
  • 1522: 准备 for 循环。在 22 处,从堆栈中将整数 0 存储到本地位置 4。那就是循环变量。
  • 2447: 循环。循环变量在 31 处被检索,并在 44 处递增。当它等于存储在本地变量 3 中的数组长度,该数组长度在 27 处进行检查时,循环结束。

结论: 它与使用索引变量显式执行 for 循环相同,不涉及迭代器。


3

对于(2),Guava提供了恰好符合您要求的内容,即Int.asList()。 相应类中的每种原语类型都有一个相应的等效类,例如Booleans代表boolean等。

    int[] arr={1,2,3};
    for(Integer i : Ints.asList(arr)) {
      System.out.println(i);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接