【入門編】Dartのコレクションにおけるスプレッド演算子(…)とNull安全の組み合わせ – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartの世界へようこそ。世界最高峰のDartコアコミッター、そしてあなたの専属メンターである私と一緒に、今日からDartの深淵を楽しく覗いていきましょう。

他の言語、例えばJavaScriptやTypeScript、あるいはJavaやPythonあたりからDartにやってきた開発者から、よくこんな質問を受けます。

「複数のリストを綺麗に結合しつつ、もし中に `null` が混ざっているかもしれない時、どう書くのが一番Dartらしい(Idiomaticな)スマートな方法ですか?」と。

実は、Dartが持つスプレッド演算子(`…`)とNull安全(Null Safety)のコンビネーションを理解すると、この問いに対する答えが驚くほど美しく見えてきます。ここをクリアすれば、あなたのDartコードは一気に洗練されたプロのそれになりますよ。

それでは、一緒に紐解いていきましょう!

—

1. スプレッド演算子(`…`)とは何か?

まずは基本のおさらいです。スプレッド演算子(`…`)は、文字通り「コレクション(ListやMap、Set)の中身を、別のコレクションの中にパカーッと広げ て展開する」ための魔法の構文です。

例えば、既存のリストをベースにして、新しい要素を追加したリストを作りたいとします。スプレッド演算子を使わない場合、どう書くでしょうか?

// 昔ながら(あるいは他言語っぽい)の書き方
final baseList = [1, 2, 3];
final newList = List.from(baseList); // 一度コピーを作って…
newList.add(4);
newList.add(5);

ちょっと冗長ですよね。これをDartのスプレッド演算子を使って書くと、こうなります。

final baseList = [1, 2, 3];
final newList = […baseList, 4, 5];

// 実行結果: [1, 2, 3, 4, 5]

どうですか?まるで文章を書くように、直感的で美しいですよね。複数のリストを合体させるのもお茶の子さいさいです。

final listA = [1, 2];
final listB = [3, 4];
final combined = […listA, …listB];

// 実行結果: [1, 2, 3, 4, 5]

裏側でDart VM(仮想マシン)やコンパイラがどう処理しているかというと、展開元のイテレータブルから効率的に要素を取り出し、新しい配列のメモリ領域へインラインでコピー・構築しています。パフォーマンス的にも非常に最適化されたコードに落ちるため、安心して使って大丈夫です。

—

2. 「Null安全」の世界で起きた悲劇と救世主 `…?`

さて、ここからが本題であり、Dartの真骨頂です。
現代のDartでは「Null安全」が完全に導入されています。つまり、デフォルトでは変数は `null` を許容しません(`String` や `List` は非Null型)。

しかし、実務の現場ではどうでしょう?
「APIから返ってきた特定のリストが、通信エラーやデータなしの影響で `null`かもしれない」という状況はよくありますよね。

ここで、もし `null` になり得る変数に対して、普通のスプレッド演算子(`…`)を使おうとするとどうなるでしょうか?

List? maybeNullList = null; // nullかもしれないリスト

// ⚠️ やってはいけない例
final result = [1, 2, …maybeNullList];

これをコンパイルしようとすると、Dartコンパイラはこう怒ってコンパイルエラーを吐き出します。
> 「おいおい、`maybeNullList` は null かもしれないのに、中身を展開しようとしただろ?安全じゃないからコンパイルエラーにするよ!」

その通りですよね。もし `null` の中身を展開しようものなら、実行時に「NullPointerException(Dartの場合は `NoSuchMethodError` や予期せぬクラッシュ)」が起きてしまいます。

救世主:Null許容スプレッド演算子(`…?`)

この問題を一発で解決するために、DartにはNull許容スプレッド演算子(`…?`)という素晴らしい機能が用意されています。

使い方は簡単。ドットの前に「`?`」を付け加えるだけです。

List? maybeNullList = null;

// 安全な書き方
final result = [1, 2, …?maybeNullList, 5];

print(result); // 実行結果: [1, 2, 5]

何が起きたかわかりましたか?
`…?maybeNullList` は、もし変数が `null` であれば、「何事もなかったかのように安全に無視(スキップ)」してくれます。一方で、もしリストの中にちゃんとデータが入っていれば、通常通り中身を展開してくれるのです。

もしこれがない世界線だったら、私たちは毎回こんなコードを書かなければならなかったでしょう。

// 昔の泥臭い書き方(こんなコードはもう書かなくていいんです!)
List result = [1, 2];
if (maybeNullList != null) {
result.addAll(maybeNullList);
}
result.add(5);

`…?` を使うだけで、宣言型かつリアクティブな美しい一行のコードに生まれ変わりますよね。

—

3. 実務でよくあるシーン:UIの条件付きリスト構築

Flutterで開発をしていると、「特定のフラグが真のときだけ、このウィジェットのリスト(あるいはデータリスト)に要素を追加したい」という場面に頻繁に出くわします。

ここでも、スプレッド演算子とNull安全を組み合わせることで、極めてスマートな記述が可能です。

void main() {
bool includeAdminFeatures = false;

final userDashboard = [
‘Home’,
‘Profile’,
// 条件によってnullを返す三項演算子と ‘…?’ のコンビネーション
…?(_isAdmin(includeAdminFeatures) ? [‘AdminPanel’, ‘Settings’] : null),
‘Logout’,
];

print(userDashboard);
// 出力結果: [Home, Profile, Logout] (AdminPanelとSettingsは安全に除外される)
}

bool _isAdmin(bool flag) => flag;

このように、「条件に合致しない場合は `null` を返し、`…?` で華麗にスルーする」というイディオムは、FlutterのWidgetツリーの構築(例えば `[if (condition) Widget()]` のようなFlutterのコレクションif構文と並んで)非常に強力な武器になります。

—

4. 陥りやすい文法エラーと注意点

初学者のうちや、他の言語から移行したときによくやってしまうミスをいくつか挙げておきます。

1. 普通の `…` に `null` を突っ込んで焦る

  • エラーメッセージを見落として、「なんで動かないんだ!」と悩むパターンです。変数に `?` がついているなら、スプレッド演算子にも `?`(つまり `…?`)が必要だと呪文のように覚えておきましょう。

2. 型の一致に気をつける