Iterable と Iterator
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 68/110。
List、Set、またはその他のコレクションに対して for-in ループを使用するたびに、内部的には Iterable と Iterator を操作しています。これらの概念を理解することで、ループ処理が可能な独自のクラスを作成できるようになります。
Iterable は、値のシーケンスを生成できる任意のオブジェクトです。これには1つの重要な要件があります。それは、Iterator を返す iterator ゲッターです。Iterator は、実際に要素を1つずつ移動するものです:
var numbers = [1, 2, 3];
var iterator = numbers.iterator;
while (iterator.moveNext()) {
print(iterator.current); // 1、次に 2、次に 3
}moveNext() メソッドは次の要素に進み、要素がある場合は true を、終了した場合は false を返します。current プロパティは現在の要素を返します。
独自のクラスを反復可能にするには、Iterableインターフェースを実装します:
class Countdown extends Iterable<int> {
final int start;
Countdown(this.start);
@override
Iterator<int> get iterator => CountdownIterator(start);
}
class CountdownIterator implements Iterator<int> {
int _current;
CountdownIterator(int start) : _current = start + 1;
@override
int get current => _current;
@override
bool moveNext() {
if (_current > 0) {
_current--;
return true;
}
return false;
}
}
void main() {
for (var n in Countdown(3)) {
print(n); // 3, 2, 1, 0
}
}これで、Countdownはfor-inループや、map、where、foldといったすべてのIterableメソッドで動作するようになります。このパターンは、Dartのコレクションエコシステムとシームレスに統合されるカスタムコレクションを構築する際に不可欠です。
チャレンジ
簡単for-in ループを使用して反復処理できるカスタム範囲ジェネレータを作成しましょう!指定された範囲内の数値シーケンスを生成するために、独自の Iterable クラスと Iterator クラスを作成します。
コードを2つのファイルに整理します:
range.dart: 数値のシーケンスを生成するために連携する2つのクラスを作成します:Iterator<int>を実装するRangeIteratorクラス。現在の位置を追跡し、いつ停止するかを認識する必要があります。イテレータは最初の要素の「前」から開始するため、currentにアクセスする前にmoveNext()を呼び出す必要があります。Iterable<int>を継承するRangeクラス。コンストラクタでstartとendの値を受け取り、RangeIteratorを返すiteratorゲッターを提供します。
startとendの両方を含む(inclusive)必要があります。例えば、Range(1, 5)は 1, 2, 3, 4, 5 を生成する必要があります。main.dart: 作成した range ファイルをインポートし、カスタム反復可能オブジェクトを実演します:Range(1, 5)を作成し、for-inループを使用して各数値をそれぞれの行に出力します。- 空行を1行出力します。
Range(10, 13)を作成し、mapメソッドを使用して各値を2倍にし、結果のリストを出力します。
Range クラスは Iterable を継承しているため、map、where、fold といった強力なコレクションメソッドに自動的にアクセスできるようになります。これが Iterable インターフェースを実装する素晴らしさです!
期待される出力:
1
2
3
4
5
[20, 22, 24, 26]チートシート
Iterable は、値のシーケンスを生成できる任意のオブジェクトです。これには、Iterator を返す iterator ゲッターが必要です。
Iterator は、以下を使用して要素を一つずつ移動します:
moveNext()- 次の要素に進み、要素がある場合はtrueを、終了した場合はfalseを返しますcurrent- 現在の要素を取得するプロパティ
イテレータを手動で使用する場合:
var numbers = [1, 2, 3];
var iterator = numbers.iterator;
while (iterator.moveNext()) {
print(iterator.current); // 1, then 2, then 3
}カスタムクラスを反復可能(iterable)にするには、Iterable インターフェースを実装し、対応する Iterator クラスを作成します:
class Countdown extends Iterable<int> {
final int start;
Countdown(this.start);
@override
Iterator<int> get iterator => CountdownIterator(start);
}
class CountdownIterator implements Iterator<int> {
int _current;
CountdownIterator(int start) : _current = start + 1;
@override
int get current => _current;
@override
bool moveNext() {
if (_current > 0) {
_current--;
return true;
}
return false;
}
}
void main() {
for (var n in Countdown(3)) {
print(n); // 3, 2, 1, 0
}
}Iterable を継承するクラスは、for-in ループで使用でき、map、where、fold などのすべての Iterable メソッドに自動的にアクセスできるようになります。
自分で試してみよう
import 'range.dart';
void main() {
// TODO: Range(1, 5)を作成し、for-inループを使用して各数値を出力してください
// TODO: 空行を出力してください
// TODO: Range(10, 13)を作成し、mapを使用して各値を2倍にし、そのリストを出力してください
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ