カスタム Iterator の実装
単純な連結リストや範囲構造のためのカスタム Iterator クラスを作成します。
「カスタム Iterator の実装」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
カスタムIterator
カスタムiteratorを構築すると、データ構造の走査方法を完全に制御できます。このレッスンでは、連結リストのiteratorを段階的に実装します。
Nodeクラス
まず、単方向連結リストのノード構造を定義します。
class Node<T> {
final T value;
Node<T> next;
Node(T value) {
this.value = value;
}
}
// Building a chain: 1 -> 2 -> 3
Node<Integer> head = new Node<>(1);
head.next = new Node<>(2);
head.next.next = new Node<>(3);Iteratorの実装
現在のノードを指すカーソルを持つIterator<T>を実装する内部クラスを作成します。
import java.util.Iterator;
import java.util.NoSuchElementException;
class LinkedList<T> implements Iterable<T> {
private Node<T> head;
private int size;
private class LinkedListIterator implements Iterator<T> {
private Node<T> current = head; // cursor
@Override
public boolean hasNext() {
return current != null;
}
@Override
public T next() {
if (!hasNext()) throw new NoSuchElementException();
T value = current.value;
current = current.next;
return value;
}
}
@Override
public Iterator<T> iterator() {
return new LinkedListIterator();
}
}addFirstと完全なLinkedList
ノードを先頭に追加する機能を実装し、完全に動作するクラスを確認します。
class LinkedList<T> implements Iterable<T> {
private Node<T> head;
private int size;
public void addFirst(T value) {
Node<T> node = new Node<>(value);
node.next = head;
head = node;
size++;
}
public void addLast(T value) {
Node<T> node = new Node<>(value);
if (head == null) { head = node; }
else {
Node<T> curr = head;
while (curr.next != null) curr = curr.next;
curr.next = node;
}
size++;
}
public int size() { return size; }
@Override
public Iterator<T> iterator() {
return new LinkedListIterator();
}
}カスタムIteratorの使用
Iterableインターフェースを実装すると、連結リストをfor-eachループやforEachで扱えるようになります。
LinkedList<String> list = new LinkedList<>();
list.addLast("Alice");
list.addLast("Bob");
list.addLast("Charlie");
// For-each loop works!
for (String name : list) {
System.out.println(name);
}
// Alice
// Bob
// Charlie
// Stream also works (Java 8+)
list.forEach(name -> System.out.println("Hello, " + name));範囲Iterator
より簡単な例として、基盤となるデータ構造を持たず、数値範囲を反復処理するiteratorを見てみましょう。
class IntRange implements Iterable<Integer> {
private final int start, end, step;
IntRange(int start, int end, int step) {
this.start = start; this.end = end; this.step = step;
}
IntRange(int start, int end) { this(start, end, 1); }
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
int current = start;
public boolean hasNext() { return current < end; }
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
int val = current;
current += step;
return val;
}
};
}
}
for (int n : new IntRange(0, 10, 2)) System.out.print(n + " ");
// 0 2 4 6 8木の中順走査Iterator
明示的なスタックを使ってBSTの中順走査iteratorを実装します。iteratorによって再帰的な走査を置き換えられることを示します。
import java.util.*;
class BinaryTree<T extends Comparable<T>> {
private record TreeNode<T>(T val, TreeNode<T> left, TreeNode<T> right) {}
private TreeNode<T> root;
public Iterator<T> inorderIterator() {
Deque<TreeNode<T>> stack = new ArrayDeque<>();
pushLeft(root, stack);
return new Iterator<>() {
public boolean hasNext() { return !stack.isEmpty(); }
public T next() {
TreeNode<T> node = stack.pop();
pushLeft(node.right(), stack);
return node.val();
}
};
}
private void pushLeft(TreeNode<T> node, Deque<TreeNode<T>> stack) {
while (node != null) { stack.push(node); node = node.left(); }
}
}遅延Iterator
Iteratorは、next()が呼び出されたときだけ値を生成する遅延処理ができます。無限シーケンスに便利です。
class FibonacciIterator implements Iterator<Long> {
private long a = 0, b = 1;
@Override public boolean hasNext() { return true; } // infinite!
@Override public Long next() {
long result = a;
long next = a + b;
a = b;
b = next;
return result;
}
}
Iterator<Long> fib = new FibonacciIterator();
for (int i = 0; i < 10; i++) System.out.print(fib.next() + " ");
// 0 1 1 2 3 5 8 13 21 34フィルターIterator
別のiteratorをラップし、述語に一致しない要素をスキップするデコレーターiteratorです。
import java.util.*;
import java.util.function.*;
class FilterIterator<T> implements Iterator<T> {
private final Iterator<T> source;
private final Predicate<T> predicate;
private T next;
private boolean hasNext;
FilterIterator(Iterator<T> source, Predicate<T> predicate) {
this.source = source; this.predicate = predicate;
advance();
}
private void advance() {
hasNext = false;
while (source.hasNext()) {
T candidate = source.next();
if (predicate.test(candidate)) { next = candidate; hasNext = true; break; }
}
}
public boolean hasNext() { return hasNext; }
public T next() { T val = next; advance(); return val; }
}
List<Integer> nums = List.of(1,2,3,4,5,6,7,8,9,10);
Iterator<Integer> evens = new FilterIterator<>(nums.iterator(), n -> n % 2 == 0);
while (evens.hasNext()) System.out.print(evens.next() + " ");
// 2 4 6 8 10IteratorとStreamの連携
カスタムiteratorは、Spliterators.spliteratorUnknownSize()を使ってStreamに適応できます。
import java.util.*;
import java.util.stream.*;
Iterator<Integer> rangeIt = new IntRange(1, 6).iterator();
Stream<Integer> stream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(rangeIt, Spliterator.ORDERED),
false // not parallel
);
int sum = stream.mapToInt(Integer::intValue).sum();
System.out.println(sum); // 15反復処理中の削除
Iteratorの任意実装メソッドremove()は、直前のnext()呼び出しで返された要素を削除します。カスタムiteratorでは明示的に実装する必要があります。
class MutableLinkedList<T> implements Iterable<T> {
// ... (full implementation)
// Iterator with remove support
private class RemovableIterator implements Iterator<T> {
private Node<T> prev = null;
private Node<T> current = head;
public boolean hasNext() { return current != null; }
public T next() {
prev = (prev == null) ? null : current;
T val = current.value;
current = current.next;
return val;
}
public void remove() {
// Remove the last returned node
if (prev == null) head = current;
else prev.next = current;
size--;
}
}
}Iteratorのチェックリスト
カスタムIteratorを実装するとき:
next()の前に必ずhasNext()を呼び出します- 空の場合、
next()からnullを返すのではなく、NoSuchElementExceptionをスローします - コレクションに依存する状態をiteratorに持たせません(コレクションのサイズをキャッシュしないでください)
- 必要に応じてmodCountを使い、同時変更を検出します
クイックチェック
要素がもうない場合、next()は何をスローすべきですか。
まとめ:カスタムIteratorの実装
重要なポイント:
- hasNext()、next()、そして任意実装のremove()を備えたIterator
を実装します - iterator内に、次の要素を指すカーソルフィールドを保持します
- hasNext()がfalseの場合、next()からNoSuchElementExceptionをスローします
- 独立したカーソルを得るため、iterator()が呼び出されるたびに新しいiteratorインスタンスを作成します
- 遅延iteratorは必要に応じて値を生成するため、無限シーケンスに便利です
- IteratorをStreamSupport.stream()でラップし、Stream APIに接続します
よくある質問
「カスタム Iterator の実装」レッスンは無料ですか?
はい。「カスタム Iterator の実装」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「カスタム Iterator の実装」で何を学びますか?
単純な連結リストや範囲構造のためのカスタム Iterator クラスを作成します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「カスタム Iterator の実装」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。