【Flutter】リストの複製に便利な…(スプレッド演算子)
はじめに
Flutter(Dart)でリストを扱う際、「あるリストを元に新しいリストを作りたい」「リスト同士を結合したい」という場面は頻繁にあります。
そんな時に活躍するのが、スプレッド演算子(Spread Operator)、通称「...(ドット3つ)」です。
今回は、単なるコピーだけでなく、Flutterの状態管理やUI構築でも必須となるこの演算子の使い方を解説します。
参考
よくある間違い:代入しただけでは「コピー」ではない
まず、スプレッド演算子を使うべき最大の理由から見ていきましょう。
以下のコードを見てください。listA を listB にコピーして、listB だけ変更したつもりですが…
void main() {
var listA = [1, 2, 3];
var listB = listA; // コピーしたつもり
listB.add(4);
print(listA); // [1, 2, 3, 4] ← !? listAも変わってしまった
print(listB); // [1, 2, 3, 4]
}
Dartにおいてリストはオブジェクトであり、= で代入するのは「参照(場所)のコピー」だからです。つまり、listA も listB もメモリ上の同じ箱を見ている状態です。
これを防ぐために、スプレッド演算子を使います。
基本的な使い方:... で展開する
スプレッド演算子を使うと、リストの中身を「展開(Spread)」して、新しいリストの中に埋め込むことができます。
void main() {
var listA = [1, 2, 3];
// listAの中身を展開して、新しい [] の中に入れる
var listB = [...listA];
listB.add(4);
print(listA); // [1, 2, 3] ← listAは無事!
print(listB); // [1, 2, 3, 4] ← listBだけ増えた
}
これにより、元のリストとは別の実体を持つ新しいリストが作成されます(いわゆる浅いコピー)。
実践テクニック 3選
スプレッド演算子は、単なる複製以外にも強力な機能を持っています。
1. リストの結合が超カンタン
addAll メソッドを使うよりも、直感的で宣言的に書くことができます。
var listA = [1, 2];
var listB = [3, 4];
// listAの後ろにlistBを結合
var combined = [...listA, ...listB];
print(combined); // [1, 2, 3, 4]
2. 要素を追加しながら複製(イミュータブルな操作)
Riverpodなどの状態管理ライブラリでは、「既存のStateを変更せず、新しいStateを作る」ことが求められます。その際にこの書き方が必須になります。
// 悪い例:state.add(newItem); // 既存のオブジェクトを変更している
// 良い例:既存のリストを展開し、末尾に新しい要素を足して、新しいリストを作る
state = [...state, newItem];
3. Null安全なスプレッド演算子 ...?
APIから取得したリストなど、元のリストが null かもしれない場合に威力を発揮します。
? をつけるだけで、「もしnullなら何もしない(クラッシュしない)」という処理になります。
List<int>? listA; // nullかもしれないリスト
// 普通の ... だと listAがnullの場合クラッシュする
// var listB = [...listA]; // Error!
// ...? ならnullの場合はスキップしてくれる
var listB = [0, ...?listA, 5];
print(listB); // [0, 5]
まとめ
-
リストの複製には
[...list]を使う。 -
リストの結合も
[...listA, ...listB]で直感的に書ける。 -
Nullの可能性がある場合は
...?listを使う。 - Riverpodなどの状態更新では必須のテクニック。
たった3つのドットですが、Dart/Flutter開発においては「使わない日はない」ほど重要な演算子です。
Discussion
Flutter(Dart)は参照渡しできない言語なので説明を変えた方がいいかと思います。
それと、スプレッド演算子は浅いコピー(シャローコピー)で、多重リストだと深い層が共有されたままなので注意が必要です。