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

时间:2010-10-12 08:25:52

标签: java arrays iterator foreach

给出以下代码片段:

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

我有以下问题:

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

13 个答案:

答案 0 :(得分:55)

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

Apache Commons Collections ArrayIterator

或者,这个,如果你想使用泛型:

com.Ostermiller.util.ArrayIterator

请注意,如果您希望Iterator超过基元类型,则不能,因为基本类型不能是通用参数。例如,如果你想要一个Iterator<int>,则必须使用Iterator<Integer>代替int[],这将导致大量自动装箱和-unboxing,如果它由{{1}}支持。

答案 1 :(得分:50)

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

引自Effective Java 2nd Ed。,第46项:

  

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

因此,您无法获得数组的Iterator(当然,除非先将其转换为List)。

答案 2 :(得分:33)

Arrays.asList(ARR).iterator();

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

答案 3 :(得分:31)

Google Guava Librarie的集合提供了这样的功能:

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

人们应该优先选择Guava而不是Apache Collection(似乎已经放弃了)。

答案 4 :(得分:14)

在Java 8中:

Arrays.stream(arr).iterator();

答案 5 :(得分: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();
  }
}

答案 6 :(得分:9)

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

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

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

答案 7 :(得分:5)

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

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

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

注意:这只是理论。你可以得到这样的迭代器,但我不鼓励你这样做。与使用“extended for syntax”的数组上的直接迭代相比,性能不佳。

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

答案 8 :(得分:4)

上述for-each循环如何工作?

与许多其他数组功能一样,JSL明确提到数组并为它们提供了神奇的属性。 JLS 7 14.14.2

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement
  

[...]

     

如果Expression的类型是Iterable的子类型,则翻译如下

     

[...]

     

否则,Expression必须具有数组类型T[]。 [[ 魔法! ]

     

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处递增。当它等于27检查时存储在局部变量3中的数组长度时,循环结束。

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

答案 9 :(得分:3)

对于(2),Guava提供了你想要的Int.asList()。关联类中的每种基本类型都有一个等价物,例如Booleansboolean等。

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

答案 10 :(得分:3)

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

1。 for-each循环如何工作?

正如Ciro Santilli 六四事件 法轮功 包卓轩指出的那样,有一个方便的实用工具可用于检查JDK附带的字节码:javap。使用它,我们可以确定以下两个代码片段产生与Java 8u74相同的字节码:

For-each循环:

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的列表不会以任何显着的方式影响性能。对记忆和表现的影响几乎是无法估量的。

Arrays.asList不使用可作为类轻松访问的普通List实现。它使用java.util.Arrays.ArrayList,与java.util.ArrayList不同。它是一个非常薄的数组包装器,无法调整大小。查看java.util.Arrays.ArrayList的源代码,我们可以看到它的设计在功能上等同于数组。几乎没有开销。请注意,我省略了除最相关代码之外的所有代码并添加了我自己的注释。

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不会创建java.util.ArrayList。它的重量更轻,适合于获得具有可忽略开销的迭代器。

原始数组

正如其他人所说,Arrays.asList不能用于原始数组。 Java 8引入了几种处理数据集合的新技术,其中一些可用于从数组中提取简单且相对高效的迭代器。请注意,如果您使用泛型,那么您总是会遇到装箱拆箱问题:您需要从int转换为Integer然后再转换为int。虽然装箱/拆箱通常可以忽略不计,但在这种情况下确实会产生O(1)性能影响,并且可能导致非常大的阵列或资源非常有限的计算机出现问题(即SoC)。

我个人最喜欢Java 8中的任何类型的数组转换/装箱操作都是新的流API。例如:

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

流API还提供了用于避免装箱问题的构造,但这需要放弃迭代器以支持流。 int,long和double分别有专门的流类型(分别是IntStream,LongStream和DoubleStream)。

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 8设计的较新代码应避免使用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,可悲的是,您最简单的选择就是简洁并且几乎肯定会涉及拳击:

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之类的轻量级内容,这并不意味着将其包装在列表中会给您带来更差的性能。

答案 11 :(得分:2)

我是最近的学生,但我相信int []的原始示例正在遍历基元数组,但不是通过使用Iterator对象。它只有相同(相似)的语法,内容不同,

for (primitive_type : array) { }

for (object_type : iterableObject) { }

Arrays.asList()APPARENTLY只将List方法应用于它给定的对象数组 - 但对于任何其他类型的对象,包括基本数组,iterator()。next()APPARENTLY只是将你的引用传递给原始对象对象,将其视为具有一个元素的列表。我们可以看到这个的源代码吗?你不想要一个例外吗?没关系。我猜(那是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());
}
}

答案 12 :(得分:1)

我喜欢使用来自番石榴的Iterators来自30thh的答案。但是,从某些框架中我得到null而不是空数组,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;
        }
    };
}