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

Emi*_*mil 76 java arrays foreach iterator

给出以下代码段:

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

我有以下问题:

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

uck*_*man 55

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

Apache Commons Collections ArrayIterator

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

com.Ostermiller.util.ArrayIterator

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

  • 同意.或者,如果要继续使用原始数据类型,请构建自己的ArrayIterator (2认同)

Pét*_*rök 50

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

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

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

所以你不能得到一个Iterator数组(当然除非把它转换成List第一个).

  • 好的我怎么能得到一个数组的迭代器? (2认同)
  • 是的,+1它只是一个普通的`for`.@Emil,你不能. (2认同)

Joc*_*hem 33

Arrays.asList(ARR).iterator();

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

  • `Arrays.asList()`不能采用`int []`,只能采用`Object []`和子类. (23认同)
  • 这是错的.这将返回一个`List <int []>`,因此返回一个`Iterator <int []>`,而不是`List <Integer>`! (3认同)

30t*_*thh 31

Google Guava Librarie的系列提供了这样的功能:

Iterator<String> it = Iterators.forArray(array);
Run Code Online (Sandbox Code Playgroud)

人们应该优先考虑Guava而不是Apache Collection(似乎已经放弃了).


小智 14

在Java 8中:

Arrays.stream(arr).iterator();
Run Code Online (Sandbox Code Playgroud)


Ash*_*iya 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();
  }
}
Run Code Online (Sandbox Code Playgroud)


Sea*_*oyd 9

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

Iterator<Integer> it = Arrays.asList(arr).iterator();
Run Code Online (Sandbox Code Playgroud)

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


Ben*_*ine 5

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

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

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

注意:这只是理论.你可以得到这样的迭代器,但我不鼓励你这样做.与使用"扩展语法"的阵列上的直接迭代相比,性能不佳.

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


Zen*_*xer 5

我有点晚了,但我注意到一些关键点被遗漏了,特别是关于 Java 8 和Arrays.asList.

\n\n

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

\n\n

作为Ciro Santilli \xe5\x85\xad\xe5\x9b\x9b\xe4\xba\x8b\xe4\xbb\xb6 \xe6\xb3\x95\xe8\xbd\xae\xe5\x8a\x9f \xe5\x8c\ x85\xe5\x8d\x93\xe8\xbd\xa9指出,有一个方便的实用程序用于检查 JDK 附带的字节码:javap。使用它,我们可以确定以下两个代码片段在 Java 8u74 中生成相同的字节码:

\n\n

对于每个循环:

\n\n
int[] arr = {1, 2, 3};\nfor (int n : arr) {\n    System.out.println(n);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

对于循环:

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

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

\n\n

虽然这不适用于基元,但应该注意的是,将数组转换为列表Arrays.asList不会对性能产生任何重大影响。对内存和性能的影响几乎是无法估量的。

\n\n

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

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

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

\n\n

同样,Arrays.asList不创建java.util.ArrayList. 它更加轻量级,适合以可忽略的开销获取迭代器。

\n\n

原始数组

\n\n

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

\n\n

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

\n\n
int[] arr = {1, 2, 3};\nIterator<Integer> iterator = Arrays.stream(arr).mapToObj(Integer::valueOf).iterator();\n
Run Code Online (Sandbox Code Playgroud)\n\n

流 API 还提供了从一开始就避免装箱问题的构造,但这需要放弃迭代器而转而使用流。int、long 和 double 有专用的流类型(分别为 IntStream、LongStream 和 DoubleStream)。

\n\n
int[] arr = {1, 2, 3};\nIntStream stream = Arrays.stream(arr);\nstream.forEach(System.out::println);\n
Run Code Online (Sandbox Code Playgroud)\n\n

有趣的是,Java 8 还添加了java.util.PrimitiveIterator. 这提供了两全其美的优点:与Iterator<T>过孔装箱兼容以及避免装箱的方法。PrimitiveIterator 具有三个对其进行扩展的内置接口:OfInt、OfLong 和 OfDouble。如果被调用,所有这三个都会装箱next(),但也可以通过诸如 之类的方法返回原语nextInt()。为 Java 8 设计的较新代码应避免使用next(),除非装箱是绝对必要的。

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

如果您还没有使用 Java 8,遗憾的是您最简单的选择不太简洁,并且几乎肯定会涉及拳击:

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

或者,如果您想创建更可重用的东西:

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

您可以通过添加自己的获取原语的方法来解决装箱问题,但它只能与您自己的内部代码一起使用。

\n\n

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

\n\n

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

\n