Iterating over List: Basic to Advance to Stream API

Hey, Tea Lovers! Collection Framework is a very powerful library in Java. Every update in Java adds some more and interesting features to this. Today, we will go over how you can iterate over a List, be it ArrayList, LinkedList or any other. We will take a look at them in ascending order from the basic loop to iteration to Stream API. And also, figure out which one is best suitable for our code. So, make your tea and get ready to sip and code.

Basic Looping over List

  • for-loop
  • enhanced for-loop
  • while-loop

By basic, I meant using the primitive loops, such as for-loop. In this, you loop with the index. You can use a while-loop similar to for-loop. And the other one is enhanced for-loop. No need of maintaining the index, you directly get the item in a defined variable. But I would suggest not to modify the list when using enhanced for-loop. Have a look at the code.

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 6, 7, 8, 10);

// basic for loop
for (int i = 0; i < list.size(); i++) {
    int data = list.get(i);
    System.out.println("Data at index " + i + " is " + data);
}

// enhanced for-loop
for (Integer data : list) {
    System.out.println("Data is "+ data +" and it is already given to you in variable");
}

// while-loop
int i = 0;
while(i < list.size()){
    int data = list.get(i);
    System.out.println("Data at index " + i + " is " + data);
    i++;
}

Advanced Iteration via iterator(s)

  • Iterator
  • ListIterator
  • For-each

Collection Framework comes with its own way of doing things. You can iterate over the list with the help of the Iterator. It gives you the Object of Iterator<E> and you can traverse through it via next().

// Iterator
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
    System.out.println("Data is " + iterator.next());
}

ListIterator<E> is kind of similar to the Iterator but has a lot more functionality. You can move backward as well and modify the current element as well.

// ListIterator
ListIterator<Integer> listIterator = list.listIterator();
while(listIterator.hasNext()){
    System.out.println("Data is " + listIterator.next());
    listIterator.set( listIterator.next() * 100);
}
System.out.println("After modifying via ListIterator ...");
while(listIterator.hasNext()){
    System.out.println("Data is " + listIterator.next());
}

In both cases, you can modify the list along the way. This may not be a good idea, since we should focus on immutability to have a maintainable code to reduce errors and debugging time in the future. And it is recommended to use enhanced for-loop instead of this.

You can also use the forEach on the list directly. You have to pass the Consumer<E> for the parameter. You can learn more about the Consumer here. With the Functional Interface, it gives you the power of lambda. WIth method reference, it becomes much more cleaner.

// forEach
list.forEach(e -> System.out.println("Data is " + e));
// you can use the method reference to print the value only
System.out.println("With method reference on for-each");
list.forEach(System.out::println);

The Pro-Level Iteration: Stream API

Before starting on stream API, I would like you to get familiar with the Stream API. I have written a post on this explaining the very basics of the Stream API on Stream API: The Hero Without a Cape.

Ok, back to the list. As you know, Stream allows you to iterate over the List or other Collection. But, unlike other iteration methods we have seen, we have intermediate and terminal functions to perform actions. A stream doesn’t start until a terminal operation is called, so it is a passive one.

The stream is a much more refined and declarative way to perform actions on the list. It doesn’t actually modify the original list. Intermediate functions do action on the element of the list in the pipeline. And again, with the help of lambda, it is so much powerful. I personally use the stream all the time. I don’t have to keep track of many things, instead just reading the functions tells me what is happening. For example, if I see filter then we are checking for some condition, the map is when we are using the element and converting it to another object and so on.

To be honest, it’s so customizable, I couldn’t figure out which one to show you guys. So I kept it simple and written comments to tell what’s happening in the code.

// stream api
// for each with stream
list.stream().forEach(System.out::println);

// using intermediate function in between
Optional<Integer> sum = list.stream()
        .filter(e -> e % 2 == 0) // only even number
        .map(e -> e * 2) // multiplying each even number by 2
        .reduce((e, acc) -> e + acc);// summing up all the data
System.out.println("Sum of even number is "+ sum.get());

You can see what is Optional doing in the code on Reduce NullPointerExceptions with Optional in Java 8 & Beyond.

Conslusion

So these were the options you have for iterating over the list. Personally, I use Stream API way more often, because I don’t need to maintain the multiple variables to keep track of what’s happening inside the loop. In case of simple operation, enhanced for loop might be a good option or forEach. But restrain yourself from using Iterators, they are things of the past and not very optimal. You can find all the code described here at GitHub. You can also all other code form coder’s tea at GitHub, GitLab, BitBucket.

See you in the next post. Till then, sip and code.