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
.