增强型for语句如何为数组工作,以及如何获取数组的迭代器?
鉴于以下代码片段:
int[] arr = {1, 2, 3}; for (int i : arr) System.out.println(i);
我有以下问题:
- 上面的每个循环是如何工作的?
- 如何获得Java中的数组的迭代器?
- 数组是否转换为列表来获取迭代器?
如果你想在一个数组上使用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必然有一个数组types
T[]
。 [[ 魔法! ]]让
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
分解:
-
0
到14
:创build数组 -
15
到22
:准备for循环。 在22 ,将堆栈中的整数0
存储到本地位置4
。 这是循环variables。 -
24
到47
:循环。 循环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; } }; }