Ways to iterate over a list in Java
Being somewhat new to the Java language I'm trying to familiarize myself with all the ways (or at least the non-pathological ones) that one might iterate through a list (or perhaps other collections) and the advantages or disadvantages of each.
Given a List<E> list
object, I know of the following ways to loop through all elements:
Basic for loop (of course, there're equivalent while
/ do while
loops as well)
// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
E element = list.get(i);
// 1 - can call methods of element
// 2 - can use 'i' to make index-based calls to methods of list
// ...
}
Note: As @amarseillan pointed out, this form is a poor choice
for iterating over List
s, because the actual implementation of
the get
method may not be as efficient as when using an Iterator
.
For example, LinkedList
implementations must traverse all of
the elements preceding i to get the i-th element.
In the above example there's no way for the List
implementation to
"save its place" to make future iterations more efficient.
For an ArrayList
it doesn't really matter, because the complexity/cost of get
is constant time (O(1)) whereas for a LinkedList
is it proportional to the size of the list (O(n)).
For more information about the computational complexity of the built-in Collections
implementations, check out this question.
Enhanced for loop (nicely explained in this question)
for (E element : list) {
// 1 - can call methods of element
// ...
}
Iterator
for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// ...
}
ListIterator
for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// 3 - can use iter.add(...) to insert a new element into the list
// between element and iter->next()
// 4 - can use iter.set(...) to replace the current element
// ...
}
Functional Java
list.stream().map(e -> e + 1); // Can apply a transformation function for e
Iterable.forEach, Stream.forEach, ...
(A map method from Java 8's Stream API (see @i_am_zero's answer).)
In Java 8 collection classes that implement Iterable
(for example, all List
s) now have a forEach
method, which can be used instead of the for loop statement demonstrated above. (Here is another question that provides a good comparison.)
Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
// (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
// being performed with each item.
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).
What other ways are there, if any?
(BTW, my interest does not stem at all from a desire to optimize performance; I just want to know what forms are available to me as a developer.)
Solution 1:
The three forms of looping are nearly identical. The enhanced for
loop:
for (E element : list) {
. . .
}
is, according to the Java Language Specification, identical in effect to the explicit use of an iterator with a traditional for
loop. In the third case, you can only modify the list contents by removing the current element and, then, only if you do it through the remove
method of the iterator itself. With index-based iteration, you are free to modify the list in any way. However, adding or removing elements that come before the current index risks having your loop skipping elements or processing the same element multiple times; you need to adjust the loop index properly when you make such changes.
In all cases, element
is a reference to the actual list element. None of the iteration methods makes a copy of anything in the list. Changes to the internal state of element
will always be seen in the internal state of the corresponding element on the list.
Essentially, there are only two ways to iterate over a list: by using an index or by using an iterator. The enhanced for loop is just a syntactic shortcut introduced in Java 5 to avoid the tedium of explicitly defining an iterator. For both styles, you can come up with essentially trivial variations using for
, while
or do while
blocks, but they all boil down to the same thing (or, rather, two things).
EDIT: As @iX3 points out in a comment, you can use a ListIterator
to set the current element of a list as you are iterating. You would need to use List#listIterator()
instead of List#iterator()
to initialize the loop variable (which, obviously, would have to be declared a ListIterator
rather than an Iterator
).
Solution 2:
Example of each kind listed in the question:
ListIterationExample.java
import java.util.*;
public class ListIterationExample {
public static void main(String []args){
List<Integer> numbers = new ArrayList<Integer>();
// populates list with initial values
for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
numbers.add(i);
printList(numbers); // 0,1,2,3,4,5,6,7
// replaces each element with twice its value
for (int index=0; index < numbers.size(); index++) {
numbers.set(index, numbers.get(index)*2);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// does nothing because list is not being changed
for (Integer number : numbers) {
number++; // number = new Integer(number+1);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// same as above -- just different syntax
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
number++;
}
printList(numbers); // 0,2,4,6,8,10,12,14
// ListIterator<?> provides an "add" method to insert elements
// between the current element and the cursor
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.add(number+1); // insert a number right before this
}
printList(numbers); // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
// Iterator<?> provides a "remove" method to delete elements
// between the current element and the cursor
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
if (number % 2 == 0) // if number is even
iter.remove(); // remove it from the collection
}
printList(numbers); // 1,3,5,7,9,11,13,15
// ListIterator<?> provides a "set" method to replace elements
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.set(number/2); // divide each element by 2
}
printList(numbers); // 0,1,2,3,4,5,6,7
}
public static void printList(List<Integer> numbers) {
StringBuilder sb = new StringBuilder();
for (Integer number : numbers) {
sb.append(number);
sb.append(",");
}
sb.deleteCharAt(sb.length()-1); // remove trailing comma
System.out.println(sb.toString());
}
}