Implementing Custom Iterators in Java with Filtering Mechanisms

Nov 22, 2025 · Programming · 9 views · 7.8

Keywords: Java Iterator | Custom Iterator | Iterator Interface | Filtered Iterator | Collection Traversal

Abstract: This article provides an in-depth exploration of implementing custom iterators in Java, focusing on creating iterators with conditional filtering capabilities through the Iterator interface. It examines the fundamental workings of iterators, presents complete code examples demonstrating how to iterate only over elements starting with specific characters, and compares different implementation approaches. Through concrete ArrayList implementation cases, the article explains the application of generics in iterator design and how to extend functionality by wrapping standard iterators on existing collections.

Iterator Fundamentals and Java Implementation Mechanism

In the Java programming language, iterators represent a crucial design pattern that provides a standardized way to sequentially access elements in a collection object without exposing its underlying representation. The java.util.Iterator interface defines the fundamental contract for iterators, and any class implementing this interface can be used in various collection traversal scenarios.

Core Implementation Methods for Custom Iterators

The most direct approach to creating custom iterators involves implementing the Iterator interface. This interface requires the implementation of three key methods: hasNext() for checking whether more elements are available for iteration, next() for retrieving the next element, and the optional remove() method for removing elements from the underlying collection.

Consider this specific requirement: we have a list containing strings ["alice", "bob", "abigail", "charlie"] and need to create an iterator that only iterates over elements starting with the letter 'a'. In such scenarios, standard collection iterators cannot meet the requirement, necessitating custom logic implementation.

Iterator Wrapper Implementation Based on Existing Collections

For situations involving existing iterable objects (such as LinkedList), the wrapper pattern can be employed to extend functionality. The core advantage of this approach lies in its broad applicability, allowing filtering capabilities to be added without modifying the original collection classes.

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class FilteredIterator<T> implements Iterator<T> {
    private final Iterator<T> baseIterator;
    private final java.util.function.Predicate<T> filter;
    private T nextElement;
    private boolean hasNextElement;
    
    public FilteredIterator(Iterator<T> baseIterator, java.util.function.Predicate<T> filter) {
        this.baseIterator = baseIterator;
        this.filter = filter;
        findNext();
    }
    
    private void findNext() {
        while (baseIterator.hasNext()) {
            T candidate = baseIterator.next();
            if (filter.test(candidate)) {
                nextElement = candidate;
                hasNextElement = true;
                return;
            }
        }
        hasNextElement = false;
        nextElement = null;
    }
    
    @Override
    public boolean hasNext() {
        return hasNextElement;
    }
    
    @Override
    public T next() {
        if (!hasNextElement) {
            throw new NoSuchElementException();
        }
        T result = nextElement;
        findNext();
        return result;
    }
    
    @Override
    public void remove() {
        throw new UnsupportedOperationException("Remove operation not supported");
    }
}

Complete Iterable Collection Implementation Solution

Another approach involves creating complete custom collection classes that implement both the Iterable interface. This method offers greater flexibility by providing complete control over iteration behavior.

import java.util.Iterator;

public class FilteredList<T> implements Iterable<T> {
    private final T[] elements;
    private final int size;
    private final java.util.function.Predicate<T> filter;
    
    public FilteredList(T[] sourceArray, java.util.function.Predicate<T> filter) {
        this.elements = java.util.Arrays.copyOf(sourceArray, sourceArray.length);
        this.size = sourceArray.length;
        this.filter = filter;
    }
    
    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            private int currentIndex = 0;
            private T nextValidElement = findNextValid();
            
            private T findNextValid() {
                while (currentIndex < size) {
                    T candidate = elements[currentIndex];
                    if (filter.test(candidate)) {
                        currentIndex++;
                        return candidate;
                    }
                    currentIndex++;
                }
                return null;
            }
            
            @Override
            public boolean hasNext() {
                return nextValidElement != null;
            }
            
            @Override
            public T next() {
                if (nextValidElement == null) {
                    throw new java.util.NoSuchElementException();
                }
                T result = nextValidElement;
                nextValidElement = findNextValid();
                return result;
            }
        };
    }
}

Practical Application Examples and Usage Scenarios

Using the filtering iterator implementations described above, the original problem requirement can be easily addressed. Below is a complete usage example:

import java.util.Arrays;
import java.util.List;

public class IteratorExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("alice", "bob", "abigail", "charlie");
        
        // Using wrapper-style filtered iterator
        Iterator<String> filteredIterator = new FilteredIterator<>(
            names.iterator(), 
            name -> name.startsWith("a")
        );
        
        System.out.println("Names starting with 'a':");
        while (filteredIterator.hasNext()) {
            System.out.println(filteredIterator.next());
        }
        
        // Using complete collection implementation
        String[] nameArray = {"alice", "bob", "abigail", "charlie"};
        FilteredList<String> filteredList = new FilteredList<>(
            nameArray, 
            name -> name.startsWith("a")
        );
        
        System.out.println("\nUsing foreach loop:");
        for (String name : filteredList) {
            System.out.println(name);
        }
    }
}

Design Considerations and Best Practices

When implementing custom iterators, several important design considerations emerge: first, iterators should be lightweight, with new instances created each time the iterator() method is called; second, boundary cases such as empty collections or no matching elements must be properly handled; finally, thread safety considerations require additional synchronization mechanisms in concurrent environments.

The wrapper approach offers advantages in generality, applicable to any existing Iterator implementation, while complete collection implementation provides better encapsulation and performance optimization opportunities. The choice between approaches depends on specific application scenarios and performance requirements.

Compatibility with Standard Iterators

Custom iterators are fully compatible with Java's enhanced for loop (foreach), provided the Iterable interface is implemented. This compatibility enables seamless integration of custom iterators into the existing Java ecosystem, working harmoniously with other collection processing tools and frameworks.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.