【入門編】Dartの「final」と「const」を使い分けるための評価タイミング・フローチャート – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartを使った開発を楽しんでいますか?
今回は、Dartの変数宣言における永遠のテーマ、`final`と`const`の使い分けについて深掘りしていきましょう。

他の言語からDartに入った開発者から、「`final`と何が違うの?」「どっちを使えばいいの?」という質問を本当によく受けます。
結論から言うと、この2つを完全に区別できるようになると、あなたの書くDartコードのパフォーマンスと意図の明確さは劇的に跳ね上がります。

ここをクリアすれば、Dartの基本はバッチリマスターできますよ。それでは、ランタイムとコンパイル時の境界を巡る旅に出発しましょう!

—

1. ざっくり理解する `final` と `const` の本質

まずは、頭の中に明確な境界線を引くためのキーワードを押さえましょう。

  • `final`(ファイナル): 「実行時(ランタイム)に値が決まり、一度だけ代入できる」
  • `const`(コンスト): 「コンパイル時(ビルド時)に値が完全確定し、完全に不変である」

「一度代入したら変えられない」という点では同じですが、「いつその値が計算・確定するのか」という時間の軸が全く異なります。

イメージしやすいように、身近なもので例えてみましょう。

  • `final` は、レストランに行って「注文した時に決まる今日のオススメ定食」(注文する=実行時にならないと何が出るか分からないけど、決まったらもう変えられない)。
  • `const` は、「お店の看板に最初からペンキで刻み込まれたメニューの価格」(お店を開く=コンパイルする前の段階で完全に固定されている)。

—

2. 評価タイミングのフローチャート

実務でコードを書くとき、「あれ、ここは `final` だっけ `const` だっけ?」と迷ったら、次のフローチャートを脳内でトレースしてください。

[変数の値を決めたい]
│
├─Q1. その値は、アプリをビルドしている最中(コードを書いている段階)に100%確定できる?
│ │
│ ├─ YES ──> const を使うべき!
│ │ (例: 3.14, “Hello”, ユーザーに見えない固定の設定値)
│ │
│ └─ NO ──> Q2へ
│
└─Q2. 値は実行時(アプリが動いてから)に決まるけど、一度入れたら二度と書き換えたくない?
│
├─ YES ──> final を使うべき!
│ (例: ログインしたユーザーの名前, APIから取得した現在時刻)
│
└─ NO ──> var または型名を指定して通常変数に!

このフローを通すだけで、迷いは完全に消え去ります。

—

3. コードで見る挙動の違いと「コンパイル時定数」の壁

では、実際のDartコードでその挙動の違いを見てみましょう。コンパイル時と実行時の境界がどこにあるのか、コメントを追いながら確認してみてください。

void main() {
// 1. var: 再代入OK、型推論あり
var name = ‘Dart’;
name = ‘Flutter’; // OK! 再代入できます

// 2. final: 実行時に値が決まる(一度だけ代入可能)
// 端末の現在時刻は、コードを書いている時には分からない(=実行時に決まる)ので final です。
final DateTime now = DateTime.now();
print(‘アプリ起動時刻: $now’);

// 下記を実行しようとすると、コンパイルエラーになります
// now = DateTime.now(); // Error: Can’t assign to the final variable ‘now’.

// 3. const: コンパイル時に値が完全に決まっていなければならない
const double pi = 3.1415926535; // OK: 数値リテラルはコンパイル時に確定します

// 【よくある罠】
// 下記はコンパイルエラーになります!なぜでしょうか?
// const DateTime currentConst = DateTime.now();
// ──> 理由: DateTime.now() は「実行時」にならないと値が計算できないからです。
// コンパイル時には値が分からないため、const には入れられません。
}

陥りやすい文法エラー:`const` にランタイムの値を入れようとする罪

初学者が最もやりがちなエラーがこれです。「変えたくないんだから全部 `const` にしちゃえ!」とやってしまうケースです。

// ❌ コンパイルエラーになる例
const String greeting = ‘こんにちは、’ + getUserName();

String getUserName() {
return ‘Alice’;
}

`getUserName()` という関数が実行されるのはアプリが動いてから(ランタイム)です。しかし `const` は「アプリを作る時(コンパイル時)」にすべてを解決しようとするため、「おい、まだ動いていない関数の結果なんて分からないよ!」とDartコンパイラに怒られてしまいます。

ここは実行時に値が決まるため、正解は `final` です。

// ⭕️ 正しい書き方
final String greeting = ‘こんにちは、’ + getUserName();

—

4. コレクション(List / Map)における `const` の深淵

ここから少しだけ高度な、Dartコアコミッターらしい話をします。Dartの `const` は、変数だけでなく「オブジェクトそのもの」をイミュータブル(不変)にします。

特にリストやマップを扱うとき、`const` はメモリ効率に絶大な効果を発揮します。

void main() {
// final なリスト
// リストの「参照(箱)」は final ですが、中身の要素は後から追加・変更できます。
final numbers = [1, 2, 3];
numbers.add(4); // OK! リスト自体はミュータブル(可変)です。

// const なリスト
// リストの箱も、中身の要素も、完全に凍結(イミュータブル)されます。
const immutableNumbers = [1, 2, 3];
// immutableNumbers.add(4); // ❌ 実行時例外 (Unsupported operation) が発生します!
}

さらに、`const` で宣言されたオブジェクトは、Dart VMのメモリ上(Constant Canonicalization:定数の同一化)で単一のインスタンスとして共有されます。
何度同じ `const [1, 2, 3]` を書いても、Dartはメモリ上に1つだけそのデータを作成し、ポインタを共有します。これにより、FlutterでのUI構築時(Widgetツリーの再描画など)のメモリ割り当てとGC(ガベージコレクション)の負荷を劇的に軽減できるのです。

—

まとめ:今日のミッション完了!

お疲れ様でした!最後に今日の重要ポイントをまとめます。

1. 迷ったらフローチャートを思い出す

  • コンパイル時に確定できる値なら `const`
  • 実行時に決まるが、一度入れたら変えたくないなら `final`

2. `DateTime.now()` などの動的な値は `const` には入れられない(`final`を使う)
3. `const` はコレクションを完全に凍結し、メモリ効率も最適化してくれる最強のカード

この違いが腹落ちすると、Dartのコードがより美しく、意図が明確なものに生まれ変わります。
明日からのコーディングで、ぜひ意識して使い分けてみてくださいね。それではまた次回の極限の知見でお会いしましょう!

タイトルとURLをコピーしました