How does the enhanced for statement work for arrays, and how to get an iterator for an array?

后端 未结 13 656
走了就别回头了
走了就别回头了 2020-11-27 04:59

Given the following code snippet:

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

I have the following questions:

相关标签:
13条回答
  • 2020-11-27 05:33

    Google Guava Libraries collection provides such function:

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

    One should prefere Guava over the Apache Collection (which seems to be abandoned).

    0 讨论(0)
  • 2020-11-27 05:34

    If you want an Iterator over an array, you could use one of the direct implementations out there instead of wrapping the array in a List. For example:

    Apache Commons Collections ArrayIterator

    Or, this one, if you'd like to use generics:

    com.Ostermiller.util.ArrayIterator

    Note that if you want to have an Iterator over primitive types, you can't, because a primitive type can't be a generic parameter. E.g., if you want an Iterator<int>, you have to use an Iterator<Integer> instead, which will result in a lot of autoboxing and -unboxing if that's backed by an int[].

    0 讨论(0)
  • 2020-11-27 05:34

    I'm a bit late to the game, but I noticed some key points that were left out, particularly regarding Java 8 and the efficiency of Arrays.asList.

    1. How does the for-each loop work?

    As Ciro Santilli 六四事件 法轮功 包卓轩 pointed out, there's a handy utility for examining bytecode that ships with the JDK: javap. Using that, we can determine that the following two code snippets produce identical bytecode as of Java 8u74:

    For-each loop:

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

    For loop:

    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. How do I get an iterator for an array in Java?

    While this doesn't work for primitives, it should be noted that converting an array to a List with Arrays.asList does not impact performance in any significant way. The impact on both memory and performance is nearly immeasurable.

    Arrays.asList does not use a normal List implementation that is readily accessible as a class. It uses java.util.Arrays.ArrayList, which is not the same as java.util.ArrayList. It is a very thin wrapper around an array and cannot be resized. Looking at the source code for java.util.Arrays.ArrayList, we can see that it's designed to be functionally equivalent to an array. There is almost no overhead. Note that I have omitted all but the most relevant code and added my own comments.

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

    The iterator is at java.util.AbstractList.Itr. As far as iterators go, it's very simple; it just calls get() until size() is reached, much like a manual for loop would do. It's the simplest and usually most efficient implementation of an Iterator for an array.

    Again, Arrays.asList does not create a java.util.ArrayList. It's much more lightweight and suitable for obtaining an iterator with negligible overhead.

    Primitive arrays

    As others have noted, Arrays.asList can't be used on primitive arrays. Java 8 introduces several new technologies for dealing with collections of data, several of which could be used to extract simple and relatively efficient iterators from arrays. Note that if you use generics, you're always going to have the boxing-unboxing problem: you'll need to convert from int to Integer and then back to int. While boxing/unboxing is usually negligible, it does have an O(1) performance impact in this case and could lead to problems with very large arrays or on computers with very limited resources (i.e., SoC).

    My personal favorite for any sort of array casting/boxing operation in Java 8 is the new stream API. For example:

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

    The streams API also offers constructs for avoiding the boxing issue in the first place, but this requires abandoning iterators in favor of streams. There are dedicated stream types for int, long, and double (IntStream, LongStream, and DoubleStream, respectively).

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

    Interestingly, Java 8 also adds java.util.PrimitiveIterator. This provides the best of both worlds: compatibility with Iterator<T> via boxing along with methods to avoid boxing. PrimitiveIterator has three built-in interfaces that extend it: OfInt, OfLong, and OfDouble. All three will box if next() is called but can also return primitives via methods such as nextInt(). Newer code designed for Java 8 should avoid using next() unless boxing is absolutely necessary.

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

    If you're not yet on Java 8, sadly your simplest option is a lot less concise and is almost certainly going to involve boxing:

    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++];
        }
    };
    

    Or if you want to create something more reusable:

    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++];
        }
    }
    

    You could get around the boxing issue here by adding your own methods for obtaining primitives, but it would only work with your own internal code.

    3. Is the array converted to a list to get the iterator?

    No, it is not. However, that doesn't mean wrapping it in a list is going to give you worse performance, provided you use something lightweight such as Arrays.asList.

    0 讨论(0)
  • 2020-11-27 05:36

    For (2), Guava provides exactly what you want as Int.asList(). There is an equivalent for each primitive type in the associated class, e.g., Booleans for boolean, etc.

        int[] arr={1,2,3};
        for(Integer i : Ints.asList(arr)) {
          System.out.println(i);
        }
    
    0 讨论(0)
  • 2020-11-27 05:37

    How does the above for-each loop work?

    Like many other array features, the JSL mentions arrays explicitly and gives them magical properties. JLS 7 14.14.2:

    EnhancedForStatement:
    
        for ( FormalParameter : Expression ) Statement
    

    [...]

    If the type of Expression is a subtype of Iterable, then the translation is as follows

    [...]

    Otherwise, the Expression necessarily has an array type, T[]. [[ MAGIC! ]]

    Let L1 ... Lm be the (possibly empty) sequence of labels immediately preceding the enhanced for statement.

    The enhanced for statement is equivalent to a basic for statement of the form:

    T[] #a = Expression;
    L1: L2: ... Lm:
    for (int #i = 0; #i < #a.length; #i++) {
        VariableModifiersopt TargetType Identifier = #a[#i];
        Statement
    }
    

    #a and #i are automatically generated identifiers that are distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the enhanced for statement occurs.

    Is the array converted to a list to get the iterator?

    Let's javap it up:

    public class ArrayForLoop {
        public static void main(String[] args) {
            int[] arr = {1, 2, 3};
            for (int i : arr)
                System.out.println(i);
        }
    }
    

    then:

    javac ArrayForLoop.java
    javap -v ArrayForLoop
    

    main method with a bit of editing to make it easier to read:

     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
    

    Breakdown:

    • 0 to 14: create the array
    • 15 to 22: prepare for the for loop. At 22, store integer 0 from stack into local position 4. THAT is the loop variable.
    • 24 to 47: the loop. The loop variable is retrieved at 31, and incremented at 44. When it equals the array length which is stored in local variable 3 on the check at 27, the loop ends.

    Conclusion: it is the same as doing an explicit for loop with an index variable, no itereators involved.

    0 讨论(0)
  • 2020-11-27 05:38
    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();
      }
    }
    
    0 讨论(0)
提交回复
热议问题