When working with Java Collections, one of the most fundamental tasks is traversing through the elements. The Iterator
interface is a core tool designed specifically to enable safe and efficient traversal of collections, while also allowing modification during iteration.
An Iterator
provides a standardized way to access elements sequentially without exposing the underlying structure of the collection. It supports three main operations:
hasNext()
: Returns true
if there are more elements to iterate over.next()
: Returns the next element in the iteration.remove()
: Removes the last element returned by next()
from the underlying collection.Using an iterator avoids potential issues like ConcurrentModificationException
that can occur if you modify a collection while iterating over it using a traditional for-loop.
import java.util.*;
public class IteratorExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry", "Date"));
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
if ("Banana".equals(fruit)) {
iterator.remove(); // Removes "Banana" safely during iteration
}
}
System.out.println("After removal: " + fruits);
}
}
Output:
Apple
Banana
Cherry
Date
After removal: [Apple, Cherry, Date]
Here, the remove()
method of the iterator safely removes the element "Banana"
during iteration without causing errors.
While Iterator
only supports forward traversal, the ListIterator
interface extends it to provide bidirectional traversal and additional functionality, but it works only with List
implementations like ArrayList
or LinkedList
.
Key features of ListIterator
:
next()
) and backward (previous()
).nextIndex()
and previousIndex()
).set(E e)
and insert new elements with add(E e)
during iteration.import java.util.*;
public class ListIteratorExample {
public static void main(String[] args) {
// Initialize the list
List<String> list = new LinkedList<>(Arrays.asList("Red", "Green", "Blue"));
// Create ListIterator
ListIterator<String> listIter = list.listIterator();
// Forward iteration
System.out.println("Forward iteration:");
while (listIter.hasNext()) {
String color = listIter.next();
System.out.println("Next: " + color);
}
// Backward iteration
System.out.println("\nBackward iteration:");
while (listIter.hasPrevious()) {
String color = listIter.previous();
System.out.println("Previous: " + color);
}
// Modify element during iteration
System.out.println("\nModifying elements:");
while (listIter.hasNext()) {
String color = listIter.next();
if ("Green".equals(color)) {
listIter.set("Yellow"); // Replaces "Green" with "Yellow"
System.out.println("Replaced 'Green' with 'Yellow'");
}
}
// Final list
System.out.println("\nModified list: " + list);
}
}
Expected Output
Forward iteration:
Next: Red
Next: Green
Next: Blue
Backward iteration:
Previous: Blue
Previous: Green
Previous: Red
Modifying elements:
Replaced 'Green' with 'Yellow'
Modified list: [Red, Yellow, Blue]
Iterator
when you want to safely traverse and optionally remove elements from any collection.ListIterator
when working specifically with lists and you need to traverse backward or modify elements in place.list.remove()
inside a loop) to prevent runtime exceptions.By mastering Iterator
and ListIterator
, you gain powerful, safe tools to navigate and manipulate collections efficiently in your Java programs.
The enhanced for-loop (also known as the for-each loop) is a convenient and concise way to iterate over elements in a collection or array. Introduced in Java 5, it simplifies iteration by hiding the complexity of the underlying iterator.
for (ElementType element : collection) {
// Use element
}
This syntax automatically retrieves each element from the collection one by one, making the code easier to read and write.
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
for (String fruit : fruits) {
System.out.println(fruit);
}
Output:
Apple
Banana
Cherry
You can also use the enhanced for-loop with other collections like Set
or arrays:
Set<Integer> numbers = new HashSet<>(Arrays.asList(10, 20, 30));
for (Integer number : numbers) {
System.out.println(number);
}
int[] scores = {90, 85, 78};
for (int score : scores) {
System.out.println(score);
}
While the enhanced for-loop is great for simple traversal, it has some limitations compared to using an explicit Iterator
:
ConcurrentModificationException
.For example, this code will fail if you try to remove elements inside the for-each loop:
for (String fruit : fruits) {
if ("Banana".equals(fruit)) {
fruits.remove(fruit); // Throws ConcurrentModificationException
}
}
The enhanced for-loop is ideal for simple, read-only iteration over collections and arrays. Its clean syntax reduces boilerplate and improves readability. However, for tasks requiring modification of collections during iteration or more control over traversal, the explicit use of Iterator
or traditional loops is necessary.
Understanding when and how to use the enhanced for-loop effectively helps you write clearer and safer Java code when working with collections.
Javaβs Stream API, introduced in Java 8, provides a modern, functional-style way to process collections. Unlike traditional iteration using loops or iterators, streams enable you to express complex data-processing queries clearly and concisely, often with better readability and parallelism support.
A stream is a sequence of elements supporting functional-style operations such as filtering, mapping, and reducing. Streams are not data structures themselves β instead, they operate on data sources like collections, arrays, or I/O channels, producing a pipeline of computations.
Key characteristics of streams:
Intermediate operations: Transform the stream and return a new stream. Examples include:
filter(Predicate<T>)
β Selects elements that match a condition.map(Function<T,R>)
β Transforms elements to another form.sorted()
β Sorts elements.Terminal operations: Produce a final result or side effect, ending the pipeline.
forEach(Consumer<T>)
β Applies an action on each element.collect(Collector<T,A,R>)
β Converts the stream into a collection or other data structure.count()
, reduce()
β Aggregate results.Suppose you have a list of fruits, and you want to get the names of all fruits starting with the letter βAβ in uppercase.
import java.util.*;
import java.util.stream.*;
public class StreamIntro {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("Apple", "Banana", "Avocado", "Cherry", "Apricot");
List<String> filtered = fruits.stream() // Create stream from list
.filter(f -> f.startsWith("A")) // Keep only fruits starting with 'A'
.map(String::toUpperCase) // Convert to uppercase
.collect(Collectors.toList()); // Collect results into a new list
System.out.println(filtered); // Output: [APPLE, AVOCADO, APRICOT]
}
}
Hereβs what happens step-by-step:
.stream()
converts the list into a stream..filter(...)
selects elements starting with βAβ..map(...)
transforms each selected fruit to uppercase..collect(...)
gathers the results into a new list.List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sumOfEven = numbers.stream()
.filter(n -> n % 2 == 0) // Keep only even numbers
.mapToInt(Integer::intValue) // Convert to IntStream for primitive operations
.sum(); // Sum all remaining numbers
System.out.println("Sum of even numbers: " + sumOfEven); // Output: 6
.parallelStream()
to boost performance on multi-core systems.The Stream API is a powerful tool that complements the Java Collections Framework by enabling functional-style data processing. It offers a declarative way to filter, transform, and aggregate data with easy-to-read syntax. While not a replacement for all collection operations, streams are an excellent choice for expressive and efficient data manipulation in modern Java programs.
import java.util.*;
public class IterationExamples {
public static void main(String[] args) {
// ===== Using Iterator with safe removal =====
List<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry", "Date"));
System.out.println("Original list: " + fruits);
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
if ("Banana".equals(fruit)) {
iterator.remove(); // Safe removal during iteration
}
}
System.out.println("After Iterator removal of 'Banana': " + fruits);
// Output: [Apple, Cherry, Date]
// ===== Using ListIterator for bidirectional traversal and modification =====
ListIterator<String> listIterator = fruits.listIterator();
System.out.println("\nForward iteration:");
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
System.out.println("Backward iteration:");
while (listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
// Modify an element safely
while (listIterator.hasNext()) {
String fruit = listIterator.next();
if ("Date".equals(fruit)) {
listIterator.set("Dragonfruit"); // Replace element safely
}
}
System.out.println("After modification with ListIterator: " + fruits);
// Output: [Apple, Cherry, Dragonfruit]
// ===== Using enhanced for-loop (for-each) =====
System.out.println("\nEnhanced for-loop iteration (read-only):");
for (String fruit : fruits) {
System.out.println(fruit);
// fruits.remove(fruit); // Uncommenting this line causes ConcurrentModificationException!
}
// ===== Using Stream API for iteration and filtering =====
System.out.println("\nStream API - filter fruits starting with 'A':");
fruits.stream()
.filter(f -> f.startsWith("A"))
.forEach(System.out::println);
// Output: Apple
// Collect filtered results into a new list
List<String> filtered = fruits.stream()
.filter(f -> f.length() > 5)
.toList();
System.out.println("Fruits with length > 5: " + filtered);
// Output: [Dragonfruit]
// ===== Common mistake: modifying collection inside enhanced for-loop =====
try {
for (String fruit : fruits) {
if ("Apple".equals(fruit)) {
fruits.remove(fruit); // Causes ConcurrentModificationException
}
}
} catch (ConcurrentModificationException e) {
System.out.println("\nCaught exception: Cannot modify collection inside enhanced for-loop!");
}
}
}
Iterator: Supports safe removal during iteration using iterator.remove()
. This prevents exceptions caused by modifying the collection while traversing.
ListIterator: Enables forward and backward traversal and safe modification of elements using set()
. Great for list-specific operations needing bidirectional access.
Enhanced for-loop: Simplifies iteration but does not allow structural modifications like removal. Attempting to remove elements inside this loop results in ConcurrentModificationException
.
Stream API: Provides a functional approach to iterate, filter, and process collections. It does not allow modification of the source during the stream operation but is powerful for querying data.
Common mistake: Directly removing elements inside an enhanced for-loop or traditional loop causes runtime errors. Always use iterator removal or collect elements to remove after iteration.
These examples demonstrate practical ways to iterate and modify collections safely while avoiding common pitfalls, helping you write robust Java code.