こんにちは!FlutterやDartの開発現場で、日々のコードをさらに洗練させたいと考えている皆さん。先輩エンジニアの私です。
今回は、Dartのコレクションリテラル(ListやSet、Map)を操る上で絶対に外せない「展開演算子(Spread Operator: `…`)」について、その基本的な使い方から、裏側でメモリがどう動いているのかという本質的な部分まで、優しく、かつ深く掘り下げて解説していきますね。
ここをクリアすれば、コレクションの操作が劇的にエレガントになり、Dartのコードが持つ「宣言的な美しさ」を完全にマスターできますよ。一緒に見ていきましょう!
—
1. 展開演算子(`…`)ってなに?基本のキ
例えば、既存のリストの中に、別のリストの要素をすべて組み込んで新しいリストを作りたいとき、あなたならどう書きますか?
昔ながらの方法だと、こんな風にループを回したり、`addAll`を使ったりしていましたよね。
// 従来の命令的な書き方
var listA = [1, 2, 3];
var listB = [4, 5];
var combined =
combined.addAll(listA);
combined.addAll(listB);
// combined は [1, 2, 3, 4, 5] になる
これ、ちょっとコードが冗長ですよね。「もっとスマートに、リストの中にリストを“展開”できたらいいのに……」その願いを叶えてくれるのが、Dartの展開演算子(`…`)です!
展開演算子を使ったモダンな書き方
void main() {
var listA = [1, 2, 3];
var listB = [4, 5];
// リストリテラルの中で … を使うだけ!
var combined = […listA, …listB];
print(combined); // 出力: [1, 2, 3, 4, 5]
}
わぁ、すごくスッキリしましたよね!
コードの意味としては、`…listA` が「`listA` の中身をバラバラにして、その場の要素として一つずつ展開しなさい」という命令になります。
—
2. ヌルセーフなDartの救世主「null許容展開演算子(`…?`)」
Dartといえば、厳格な「Null安全(Null Safety)」が特徴ですよね。もし展開したい元のリストが `null` だった場合、通常の `…` を使うとどうなると思いますか?
そう、「NullPointerExceptionならぬ、NullThrownError(正確にはNull is not a subtype of type…)」のような実行時エラーになってしまいます。
そこで登場するのが、null許容展開演算子(`…?`)です!
void main() {
List
var baseList = [1, 2, 3];
// 展開したいリストが null かもしれないときは …? を使う
var result = […baseList, …?maybeNullList];
print(result); // 出力: [1, 2, 3] (エラーにならず、nullは無視される!)
}
「もし中身が `null` なら、何もないものとしてスルーしてね」という安全装置です。APIから返ってきたリストが偶発的に `null` を返すかもしれない現場では、この `…?` が文字通り命綱になります。必ずセットで覚えておきましょう!
—
3. 制御構文いらず!コレクション内での条件分岐とループ
展開演算子の真骨頂は、単にリストを合体させるだけではありません。Dartのコレクションリテラル内では、`if` や `for` などの制御構文をそのまま直書きできる(Collection For / If)という強力な特徴があります。
これらを組み合わせると、UIの構築(FlutterのWidgetツリーなど)やデータの加工が圧倒的に宣言的になります。
void main() {
bool includeAdminPrivileges = true;
var basePermissions = [‘read’, ‘write’];
var userPermissions = [
‘login’,
// 条件に一致したときだけ要素を追加する
if (includeAdminPrivileges) …[‘delete’, ‘manage’],
…basePermissions,
];
print(userPermissions);
// 出力: [login, delete, manage, read, write]
}
わざわざ `if` 文の外で空のリストを作って `.add()` しまくる必要はもうありません。コードの意図がひと目でわかる、美しく健全なコードですね。
—
4. 【本質を知る】メモリ効率と裏側の仕組み
さて、ここからが少し踏み込んだ「チーフアーキレク トからの視点」です。
「コードが綺麗になるのはわかったけど、パフォーマンスはどうなの?」という疑問を持つあなたは、すでに中級者以上のエンジニアのセンスを持っています。
内部で何が起きているか?
Dart VM(およびAOTコンパイラ)において、`[…listA, …listB]` のようなリテラル評価は、「最終的に必要な要素の総数をあらかじめ計算し、そのサイズにぴったりのメモリ領域(内部配列)を一度の操作で確保する」ように最適化されます。
命令的に `.add()` を繰り返すと、リストの容量(Capacity)が足りなくなるたびに裏側でメモリの再割りAllocation(再確保)とコピーが発生し、ガベージコレクタ(GC)に不要な負担をかける原因になります。
しかし、コレクションリテラルと展開演算子を使った宣言的記法であれば、コンパイラとVMが効率的なアロケーション経路を選択しやすくなります。
⚠️ 注意すべきアンチパターン
ただし、何でもかんでも展開すればいいというわけではありません。以下の例を見てください。
// ⚠️ やりがちなアンチパターン
var heavyList = List.generate(1000000, (i) => i);
// 毎回巨大なリスト全体を展開して新しいリストを作っている
var optimizedOrNot = [
…heavyList,
999,
];
数百万件もある巨大なコレクションに対して、毎回 `…` で全要素をコピーして新しいインスタンスを作り直していると、メモリ帯域を圧迫し、UIスレッドでカクつき(Jank)を引き起こす原因になります。
- 少〜中規模のリストや、UIの要素構築(Flutterのchildrenなど): 展開演算子をガンガン使ってコードの読みやすさを優先する。
- 巨大なデータセットや、ストリーム処理、頻繁に更新されるデータ: 不必要なコピーを避け、必要に応じてビュー(Views)やイテレータの活用を検討する。
この「トレードオフ」を意識できるようになると、あなたの書くDartコードの信頼性は一段と跳ね上がりますよ。
—
まとめ
今回はDartのコレクションリテラルと展開演算子(`…` / `…?`)について解説しました。
- `…`:複数のリストを安全かつスマートに結合する。
- `…?`:Null安全に対応し、対象が `null` なら安全に無視する。
- コレクション内 `if` / `for`:制御構文をインラインで書き、宣言的なデータ構築を行う。
- パフォーマンス:無駄なメモリ再割り当てを防ぐスマートな評価をしつつ、巨大データの過剰なコピーには注意する。
ここをクリアすれば、あなたのDartの基礎力はもうバッチリマスターできています!
ぜひ今日の開発から、無駄な `.addAll()` をスタイリッシュな `…` に置き換えてみてくださいね。それではまた次回の技術解説でお会いしましょう!