增强型for语句如何为数组工作,以及如何获取数组的迭代器?

鉴于以下代码片段:

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

我有以下问题:

  1. 上面的每个循环是如何工作的?
  2. 如何获得Java中的数组的迭代器?
  3. 数组是否转换为列表来获取迭代器?

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

Apache Commons Collections ArrayIterator

或者,这一个,如果你想使用generics:

com.Ostermiller.util.ArrayIterator

请注意,如果您希望在基本types上使用Iterator ,则不能,因为基元types不能是generics参数。 例如,如果你想要一个Iterator<int> ,你必须使用一个Iterator<Integer>来代替,如果有一个int[]支持的话,将会产生大量的自动装箱和–unboxing。

不,没有转换。 JVM只是在后台使用索引遍历数组。

Effective Java第二版引用,第46项:

请注意,使用for-each循环没有性能损失,即使对于数组也是如此。 实际上,在某些情况下,它可能会比普通的for循环提供轻微的性能优势,因为它只计算一次数组索引的极限。

所以你不能得到一个数组的Iterator (除非先把它转换成一个List )。

Arrays.asList(ARR).iterator();

或者写你自己的,实现ListIterator接口..

Google Guava Librarie的collections提供了这样的function:

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

一个人应该优先考虑番石榴的Apache收集(这似乎被放弃)。

在Java 8中:

 Arrays.stream(arr).iterator(); 
 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(); } } 

严格地说,你不能得到原始数组的迭代器,因为Iterator.next()只能返回一个Object。 但是通过自动装箱的魔法,你可以使用Arrays.asList()方法获得迭代器。

 Iterator<Integer> it = Arrays.asList(arr).iterator(); 

上面的答案是错误的,你不能在一个基本数组上使用Arrays.asList() ,它会返回一个List<int[]> 。 使用番石榴的Ints.asList()代替。

你不能直接得到一个数组的迭代器。

但是你可以使用List来支持你的数组,并且在这个列表中得到一个确定器。 为此,你的数组必须是一个整数数组(而不是一个int数组):

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

注意:这只是理论。 你可以得到这样的迭代器,但我劝阻你这样做。 性能相比直接迭代arrays的“扩展语法”不好。

注2:使用此方法的列表构造不支持所有方法(因为列表由具有固定大小的数组支持)。 例如,迭代器的“remove”方法将导致exception。

上面的每个循环是如何工作的?

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

 EnhancedForStatement: for ( FormalParameter : Expression ) Statement 

[…]

如果Expression的types是Iterable的子Iterable ,那么翻译如下

[…]

否则,Expression必然有一个数组typesT[] 。 [[ 魔法! ]]

L1 ... Lm是紧接在增强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语句发生处的范围内的任何其他标识符(自动生成或其他)不同。

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

让我们来了解它:

 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 :创build数组
  • 1522 :准备for循环。 在22 ,将堆栈中的整数0存储到本地位置4 。 这是循环variables。
  • 2447 :循环。 循环variables在31处被检索,并且在44处被增加。 当它等于在27处的检查中存储在局部variables3中的数组长度时,循环结束。

结论 :这与使用索引variables进行循环显式相同,不涉及迭代器。

对于(2),番石榴确切地提供你想要的Int.asList() 。 每个关联类中的基本types都有一个等价的,例如Booleans等的Booleans boolean

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

我有点晚了,但我注意到了一些关键点,特别是关于Java 8和Arrays.asList的效率。

1. for-each循环是如何工作的?

正如Ciro Santilli指出的那样,检查JDK提供的字节码有一个方便的实用工具: javap 。 使用这个,我们可以确定以下两个代码片段产生相同的字节码,如Java 8u74:

对于每个循环:

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

For循环:

 int[] arr = {1, 2, 3}; { // These extra braces are to limit scope; they do not affect the bytecode int[] iter = arr; int length = iter.length; for (int i = 0; i < length; i++) { int n = iter[i]; System.out.println(n); } } 

2.如何获得Java中的数组的迭代器?

虽然这不适用于基元,但应该注意的是,使用Arrays.asList将数组转换为列表不会影响性能。 对记忆和performance的影响几乎是不可估量的。

Arrays.asList不使用一个普通的List实现,这个实现很容易作为一个类来访问。 它使用java.util.Arrays.ArrayList ,它与java.util.ArrayList 。 这是一个非常薄的数组包装,不能resize。 查看java.util.Arrays.ArrayList的源代码,我们可以看到它被devise为在function上等同于一个数组。 几乎没有开销。 请注意,我已经省略了所有最相关的代码,并添加了我自己的意见。

 public class Arrays { public static <T> List<T> asList(T... a) { return new ArrayList<>(a); } private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } @Override public int size() { return a.length; } @Override public E get(int index) { return a[index]; } @Override public E set(int index, E element) { E oldValue = a[index]; a[index] = element; return oldValue; } } } 

迭代器位于java.util.AbstractList.Itr 。 就迭代器而言,这非常简单; 它只是调用get()直到达到size() ,就像循环的手册一样。 这是一个数组的Iterator的最简单和通常最有效的实现。

同样, Arrays.asList不会创build一个java.util.ArrayList 。 它更轻量级,适合于获得可忽略的开销的迭代器。

原始数组

正如其他人已经指出, Arrays.asList不能用于原始数组。 Java 8引入了一些处理数据集合的新技术,其中有些可以用来从数组中提取简单且相对高效的迭代器。 请注意,如果你使用generics,你总是会有拳击拆箱的问题:你需要从int转换为Integer,然后返回到int。 虽然装箱/取消装箱通常是微不足道的,但是在这种情况下它确实对O(1)性能有影响,并且可能会导致非常大的数组或者资源非常有限的计算机(即SoC )出现问题。

我个人最喜欢Java 8中的任何一种数组转换/装箱操作,都是新的streamAPI。 例如:

 int[] arr = {1, 2, 3}; Iterator<Integer> iterator = Arrays.stream(arr).mapToObj(Integer::valueOf).iterator(); 

streamAPI也提供了用于避免装箱问题的构造,但是这需要放弃迭代器来支持stream。 有int,long和double(分别为IntStream,LongStream和DoubleStream)的专用streamtypes。

 int[] arr = {1, 2, 3}; IntStream stream = Arrays.stream(arr); stream.forEach(System.out::println); 

有趣的是,Java 8还添加了java.util.PrimitiveIterator 。 这提供了最好的两个世界:与Iterator<T>兼容通过拳击以及避免拳击的方法。 PrimitiveIterator有三个内置的接口来扩展它:OfInt,OfLong和OfDouble。 如果next()被调用,所有三者都将会出现,但是也可以通过nextInt()等方法返回原语。 为Java 8devise的新代码应该避免使用next()除非装箱是绝对必要的。

 int[] arr = {1, 2, 3}; PrimitiveIterator.OfInt iterator = Arrays.stream(arr); // You can use it as an Iterator<Integer> without casting: Iterator<Integer> example = iterator; // You can obtain primitives while iterating without ever boxing/unboxing: while (iterator.hasNext()) { // Would result in boxing + unboxing: //int n = iterator.next(); // No boxing/unboxing: int n = iterator.nextInt(); System.out.println(n); } 

如果你还没有在Java 8上,可惜你最简单的select是简单得多,几乎肯定会涉及拳击:

 final int[] arr = {1, 2, 3}; Iterator<Integer> iterator = new Iterator<Integer>() { int i = 0; @Override public boolean hasNext() { return i < arr.length; } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } return arr[i++]; } }; 

或者如果你想创造更多可重用的东西:

 public final class IntIterator implements Iterator<Integer> { private final int[] arr; private int i = 0; public IntIterator(int[] arr) { this.arr = arr; } @Override public boolean hasNext() { return i < arr.length; } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } return arr[i++]; } } 

你可以通过添加你自己的获取原语的方法来解决这个问题,但是只能用你自己的内部代码。

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

不它不是。 但是,这并不意味着将它包装在一个列表中会给你带来更糟的性能,只要你使用一些轻量级的东西如Arrays.asList

我是一个最近的学生,但我相信原始示例与int []迭代在基元数组,但不是通过使用Iterator对象。 它仅仅具有不同内容的相同(相似)的语法,

 for (primitive_type : array) { } for (object_type : iterableObject) { } 

Arrays.asList()显然只是将List方法应用到给定的对象数组,而对于任何其他types的对象,包括基本数组iterator()next()显然只是把你引用原始对象,它作为一个元素的列表。 我们可以看到这个源代码吗? 你不喜欢一个例外吗? 没关系。 我猜(这是GUESS),它就像(或它是)一个单独的集合。 所以这里asList()与基元数组无关,但是令人困惑。 我不知道我是对的,但我写了一个程序,说我是。

因此,这个例子(基本上asList()不会做你认为会这样做,因此不是你实际上使用这种方式) – 我希望代码比我的代码标记更好,嘿,看看最后一行:

 // Java(TM) SE Runtime Environment (build 1.6.0_19-b04) import java.util.*; public class Page0434Ex00Ver07 { public static void main(String[] args) { int[] ii = new int[4]; ii[0] = 2; ii[1] = 3; ii[2] = 5; ii[3] = 7; Arrays.asList(ii); Iterator ai = Arrays.asList(ii).iterator(); int[] i2 = (int[]) ai.next(); for (int i : i2) { System.out.println(i); } System.out.println(Arrays.asList(12345678).iterator().next()); } } 

我喜欢使用来自Guava的Iterators的答案。 但是,从一些框架我得到空,而不是一个空的数组, Iterators.forArray(array)处理得不好。 所以我想出了这个辅助方法,你可以调用Iterator<String> it = emptyIfNull(array);

 public static <F> UnmodifiableIterator<F> emptyIfNull(F[] array) { if (array != null) { return Iterators.forArray(array); } return new UnmodifiableIterator<F>() { public boolean hasNext() { return false; } public F next() { return null; } }; }