The ability to iterate over a collection is fundamental in any programming language. In Java, this is achieved using the Iterable
and Iterator
interfaces — core components of the Java Collections Framework.
But what if you want to define your own data structure and still support enhanced for-loops and streaming APIs? That's where creating a custom Iterable
and Iterator
becomes invaluable.
This guide will walk you through the purpose, implementation, and best practices of building your own iterable collections, whether for interview preparation, library development, or educational use.
What Are Iterable and Iterator in Java?
📌 Iterable<T>
- Introduced in Java 5.
- Interface with a single method:
Iterator<T> iterator();
- Enables enhanced for-loop (
for-each
) support.
📌 Iterator<T>
- Interface with three primary methods:
boolean hasNext(); T next(); default void remove(); // Optional
Built-in Usage
List<String> names = List.of("Alice", "Bob");
for (String name : names) {
System.out.println(name);
}
Why Create a Custom Iterable?
- You have a custom data structure (e.g., a circular buffer or matrix).
- You want to control iteration behavior.
- You’re building a framework, API, or domain-specific collection.
- For better testability and encapsulation.
Step-by-Step Example: Creating a Custom Iterable
Let’s create a custom Range
class that iterates over a sequence of integers.
Step 1: Create the Iterable Class
public class Range implements Iterable<Integer> {
private final int start;
private final int end;
public Range(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Iterator<Integer> iterator() {
return new RangeIterator();
}
private class RangeIterator implements Iterator<Integer> {
private int current = start;
@Override
public boolean hasNext() {
return current <= end;
}
@Override
public Integer next() {
return current++;
}
}
}
Step 2: Use the Iterable
public class TestRange {
public static void main(String[] args) {
for (int i : new Range(1, 5)) {
System.out.println(i);
}
}
}
Output:
1
2
3
4
5
Functional Support with Java 8+
Stream Support via Spliterator (Optional Advanced)
To make your custom Iterable
streamable:
StreamSupport.stream(range.spliterator(), false)
.map(n -> n * 2)
.forEach(System.out::println);
You can override spliterator()
for optimized performance.
Internal Mechanics
- The enhanced
for
loop is syntactic sugar for callingiterator()
, then looping usinghasNext()
andnext()
. Iterator
is stateful — it tracks where you are in the traversal.
Performance Considerations
Factor | Impact |
---|---|
Memory efficiency | Depends on internal state logic |
Thread safety | Custom iterator not thread-safe by default |
Big-O complexity | Depends on backing structure |
Real-World Use Cases
- Database cursors – Custom iterator to fetch rows
- Pagination wrappers – Iterate over paginated APIs
- Custom DSLs or config loaders
- File readers that lazily load lines
Comparisons
Feature | Iterable | Iterator |
---|---|---|
Purpose | Represents collection | Represents iteration |
Method count | 1 (iterator() ) |
2–3 (hasNext() , next() , remove() ) |
Supports for-each | ✅ Yes | ❌ No (not directly) |
Functional friendly | ✅ (via Streams) | ✅ (manual conversion) |
Java Version Tracker
📌 What's New in Java?
- Java 8
- Default methods like
remove()
inIterator
StreamSupport.stream()
forIterable
- Default methods like
- Java 9
- Enhanced support for
Spliterator
API
- Enhanced support for
- Java 10+
var
for inferring local variable types
- Java 21
- No direct changes to
Iterable
/Iterator
, but better parallel stream support
- No direct changes to
Best Practices
- Make iterators fail-fast if your structure is modifiable
- Avoid keeping mutable shared state in the iterator
- Consider implementing
remove()
if mutation is meaningful - Document behavior clearly (e.g., inclusive/exclusive ranges)
- Return
Iterable<T>
from APIs when possible — more flexible thanCollection<T>
Anti-Patterns
- Returning null from
next()
— always throwNoSuchElementException
if exhausted - Modifying the collection structure during iteration without proper checks
- Overcomplicating iteration logic when simple looping suffices
Refactoring Legacy Code
- Convert indexed iteration to custom
Iterable
for better encapsulation - Wrap non-iterable structures like arrays or result sets in custom
Iterable
- Replace anonymous iterator classes with lambda-based generators (Java 8+)
Conclusion and Key Takeaways
Iterable
andIterator
are cornerstones of collection traversal in Java.- Creating custom implementations improves flexibility, testability, and abstraction.
- Works seamlessly with enhanced for-loops and Java 8 Streams.
- Make sure your custom iterator is correct, efficient, and predictable.
FAQ – Custom Iterable and Iterator
-
Can I make my class Iterable and Serializable?
Yes, they’re unrelated interfaces. -
Do I need both Iterable and Iterator interfaces?
Yes —Iterable
is the container,Iterator
is the traversal mechanism. -
Can I use enhanced for-loop on my custom class?
Yes, if it implementsIterable<T>
. -
What happens if I call
next()
with nohasNext()
?
It should throwNoSuchElementException
. -
Is my custom iterator reusable?
No. Iterators are typically one-time use. -
Can I stop iteration early?
Yes — break the loop or filter with Streams. -
Is it OK to return null in
next()
?
Discouraged. Use exceptions for end-of-iteration. -
Are custom Iterators thread-safe?
Not by default — add synchronization if needed. -
Can I create an infinite iterator?
Yes, but be careful — always use with a limit condition. -
Should I use Iterable for APIs or Iterator?
Prefer returningIterable<T>
— it's more flexible.