Composite & Iterator in Java
Tree structures treated uniformly; traversal abstraction.
Composite: one interface for parts and wholes
Composite lets a single object and a group of objects share one interface. A leaf answers directly; a container recurses into its children. Callers never check which kind they have, just like an order from a general flowing down to every soldier.
interface Node { int size(); }
record Leaf(int size) implements Node {}
record Dir(List<Node> kids) implements Node {
public int size() { return kids.stream()
.mapToInt(Node::size).sum(); }
}Folders inside folders
What does this print?
interface Node { int size(); }
record Doc(int size) implements Node {}
record Dir(Node... kids) implements Node {
public int size() { return Stream.of(kids)
.mapToInt(Node::size).sum(); }
}
void main() {
var sub = new Dir(new Doc(5), new Doc(1));
IO.println(new Dir(new Doc(2), sub).size());
}8236
Show the answer
The outer Dir sums its children: Doc(2) plus the inner Dir, which sums its own children 5 + 1 = 6. Total 8. A Dir can hold both docs and dirs because **both implement Node**.
Iterator: walk without peeking inside
Iterator separates traversal from the collection's internals. Java's for-each loop just calls iterator(), then hasNext() and next(). So **any class implementing Iterable<T>** works in for-each, not only collections.
record Range(int from, int to)
implements Iterable<Integer> {
public Iterator<Integer> iterator() {
return IntStream.range(from, to)
.iterator();
}
}
// for (int i : new Range(1, 4)) ...Deleting during for-each
Removing through the list behind the iterator's back changes the list's internal modification count. The iterator notices on the next next() and fails fast with ConcurrentModificationException. Wrapping it in try/catch just leaves the list half-processed.
var xs = new ArrayList<>(List.of(1, 2, 3, 4));
for (Integer x : xs) {
if (x % 2 == 0) xs.remove(x); // boom
}Removing safely
for (Integer x : xs) {
if (x % 2 == 0) xs.remove(x);
}ConcurrentModificationException.
xs.removeIf(x -> x % 2 == 0);
// or: Iterator<Integer> it = xs.iterator();
// ... it.remove();removeIf, or Iterator.remove(), deletes in a way the iteration knows about.
Name the pattern
In Swing, a JPanel is a Component that can contain other Components, including more JPanels. Which pattern is that?
Think about it, then reveal the answer
Composite. Containers and leaf widgets share the Component type, so layout and painting recurse through the tree uniformly. File systems, UI trees, menus and JSON documents all have the same shape.
Everywhere in practice
UI frameworks, file trees, org charts and expression trees are composites. ConcurrentModificationException is a very common production bug in loops that clean up lists; reviewers look for remove inside for-each and suggest removeIf.
Key takeaways
- Composite: leaf and container share one interface
- Operations on a container recurse into its children
- Iterator hides the collection's internal structure
- Implement Iterable<T> to support for-each
💡 An army: an order given to a general flows down to every squad and soldier the same way.
The enhanced for loop and the java.lang.Iterable interface arrived together in Java 5 (2004). Iterable was added precisely so any class could plug into for-each.
Practice questions
What does this print?
interface Node { int size(); }
record Leaf(int size) implements Node {}
record Dir(List<Node> kids) implements Node {
public int size() { return kids.stream()
.mapToInt(Node::size).sum(); }
}
void main() {
IO.println(new Dir(List.of(new Leaf(3),
new Dir(List.of(new Leaf(4))))).size());
}- 3
- 2
- 4
- 7
Check your answer
7. The outer Dir sums its children: Leaf 3 plus the inner Dir, which sums its own child, 4. The caller never checks which kind of node it has.
What does this print?
var xs = new ArrayList<>(List.of(1, 2, 3, 4));
for (Integer x : xs) {
if (x % 2 == 0) xs.remove(x);
}
System.out.println(xs);- [1, 3]
- Throws ConcurrentModificationException
- [1, 3, 4]
- Compile error
Check your answer
Throws ConcurrentModificationException. The for-each loop uses the list's iterator. Removing through the list behind the iterator's back changes modCount, and the next call to next() fails fast.