增强的for语句如何用于数组,以及如何获取数组的迭代器?


76

给出以下代码片段:

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

我有以下问题:

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


以上有关性能的问题已解决。它是在这里找到答案:stackoverflow.com/questions/256859/...
Orangle

我找不到确定的第1点的答案。for each循环是否测试要迭代的项的类型,并且如果是数组,则在幕后使用常规的for循环?还是会动态创建Iterable?还是转换为列表?我还没有找到确切的答案。
伊丽莎白

Answers:


55

如果您要Iterator遍历数组,则可以使用其中的直接实现之一,而不用将数组包装在中List。例如:

Apache Commons集合 ArrayIterator

或者,如果您想使用泛型,请执行以下操作:

com.Ostermiller.util.ArrayIterator

请注意,如果您想拥有一个Iterator超过基本类型的对象,则不能这样做,因为基本类型不能是通用参数。例如,如果要使用Iterator<int>,则必须Iterator<Integer>改用,如果后面有,则会导致大量自动装箱和-unboxing int[]


2
同意。如果您想继续使用原始数据类型,请构建自己的ArrayIterator
Khaled.K 2014年

代码完成不显示任何建议ArrayIt;假设所有第三方图书馆。
Mark Jeronimus

50

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

引用来自有效Java第二版,条款46:

请注意,即使对于数组,使用for-each循环也不会降低性能。实际上,在某些情况下,它可能只比普通的for循环提供一点性能优势,因为它只计算一次数组索引的限制。

因此,您无法获得Iterator用于数组的(除非将其转换为第一个数组List)。


2
是的,+ 1只是一个普通的for。@Emil,你不能。
st0le

如果有效Java是一个空Collection,那么它是错误的。在这种情况下,它将不必要地创建Iterator。这导致对象流失。解决方法是将其包装在isEmpty()检查中。这对于像Android这样受内存限制的设备尤其重要。
keyboardr

我们确定它没有为数组创建Iterable,还是将数组转换为List?换句话说,Java内部说“如果类型是数组,则使用带有隐藏索引的常规for循环,否则使用Iterable”。???
伊丽莎白

33

Arrays.asList(arr).iterator();

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


23
Arrays.asList()不能使用int[]Object[]和子类。
ILMTitan 2010年

3
这是错误的。这将返回一个List<int[]>,因此返回一个,而Iterator<int[]>不是一个List<Integer>
njzk2 2014年

您要将数组转换为列表只是为了提供迭代器?!
Khaled.K 2014年

1
@KhaledKhnifer转换确实没有太多。Arrays.asList()使用实际的数组并将其包装在一个使它适合列表接口的类中。因此,除了“不支持原语”问题(对于在非原始情况下遇到相同问题的人)而言,这实际上是一个很好的解决方案。
贾斯珀

@ Khaled.K,请参阅Zenexer(stackoverflow.com/users/1188377/zenexer)的新答案。
Orangle

31

Google Guava Librarie的集合提供了以下功能:

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

人们应该更喜欢Guava而不是Apache Collection(后者似乎已被放弃)。


1
通常这是最好的答案,除非当我们处理原始数组(例如int[])时,这将不再起作用:(因为我们无法指定Iterator<int>Iterator<Integer>
chakrit 2014年


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();
  }
}


5

您不能直接获得数组的迭代器。

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

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

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

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


我认为这不会起作用。Arrays.asList(arr)将返回int []类型的List。因为它是原始数组。
埃米尔(Emil)2010年

@Emil:Java泛型不适用于原始类型。Arrays.asList通过将数组中的值装箱来隐式地解决此问题。因此,结果真的一个List<Integer>
康拉德·鲁道夫

@emil之所以有效,是因为Arrays.asList使用varargs参数
肖恩·帕特里克·弗洛伊德

@seanizer:但是我在IDE中尝试了上面的代码,但显示错误。
埃米尔(Emil)2010年

它在我的日食中编译。您是否已将编译器兼容性设置为至少1.5?
肖恩·帕特里克·弗洛伊德

4

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

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

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

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

[...]

否则,表达式必须具有数组类型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

分解:

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

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


4

我玩游戏有些迟了,但是我注意到一些关键点被遗漏了,特别是在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);
}

对于循环:

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中获得数组的迭代器?

尽管这不适用于原语,但应注意,将数组转换为具有的ListArrays.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()到达,就像手动for循环一样。这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>via装箱以及避免装箱的方法兼容。PrimitiveIterator具有三个扩展它的内置接口:OfInt,OfLong和OfDouble。如果next()调用,这三个函数都将置为box,但也可以通过诸如之类的方法返回原语nextInt()next()除非绝对需要装箱,否则应避免使用为Java 8设计的较新代码。

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


1
我认为,最完整的答案是,但我认为您应该替换mapToObj(Integer::new)mapToObj(Integer::valueOf)
MichaelSchmeißer16年

看来您说得对,@MichaelSchmeißer。谢谢你的提示。我已经相应地更新了答案。
Zenexer '16

3

对于(2),Guava提供的正是Int.asList()。关联类中的每个原始类型都有一个等效项,例如Booleansforboolean等。

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

2

我是一个新来的学生,但是我相信int []的原始示例是遍历图元数组,而不是通过使用Iterator对象。它只有具有不同内容的相同(相似)语法,

for (primitive_type : array) { }

for (object_type : iterableObject) { }

Arrays.asList()大概只是将List方法应用于给定的对象数组-但是对于任何其他类型的对象,包括原始数组,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());
}
}

1

我喜欢使用Iterators番石榴提供的30号答案。但是,从某些框架中,我得到的是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;
        }
    };
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.