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

こんにちは!FlutterやDartの開発現場で、日々コードの海に潜っている先輩エンジニアです。

Dartを学び始めると、すぐにぶつかるのが変数の宣言方法の壁ですよね。「`var`はなんとなく分かるけれど、`final`と`const`って何が違うの?」「どっちを使えばいいの?」と手が止まってしまうことはありませんか?

ここをクリアすれば、Dartのメモリ管理やコードの意図がぐっと明確になり、ワンランク上のコードが書けるようになりますよ。今日は、Dartの心臓部である「評価タイミング」という視点から、`final`と`const`を完璧に使い分けるための極意を、優しく紐解いていきましょう!

—

1. ざっくり理解する:`final` と `const` の決定的な違い

まずは結論からお話ししますね。`final` と `const` の違いは、一言で言うと「いつ値が決まるか(評価されるか)」の違いです。

  • `final`(実行時定数): アプリが「動いている最中(実行時)」に1度だけ値が決まる。
  • `const`(コンパイル時定数): アプリが「ビルドされる時(コンパイル時)」にはすでに値が完全に決まっている。

この「決まるタイミング」の差が、Dart VM(仮想マシン)のメモリ効率や、コードの振る舞いに大きな影響を与えます。

—

2. 脳内トレース用!「final vs const」決定フローチャート

実際の開発現場で「どっちを使うべき?」と迷ったら、頭の中でこのフローチャートを思い浮かべてみてください。

[ 変数を宣言したい ]
│
▼
Q. 値は「アプリのビルド時(コードを書いている時点)」に絶対確定している?
│
├── 【YES】 ──► Q. アプリ全体で使い回す不変のデータ(ウィジェットの設定など)?
│ │
│ ├── 【YES】 ──► 【 const 】 を採用! (完全なコンパイル時定数)
│ │
│ └── 【NO】 ──► 【 const 】 または 【 final 】 (文脈による)
│
└── 【NO】 ──► (ユーザーの入力、API通信の結果、現在時刻など)
│
▼
【 final 】 を採用! (実行時に一度だけ代入)

非常にシンプルですよね。「未来の値(実行時にならないと分からないもの)」は `final`、「過去から未来永劫変わらない絶対的な定数(コンパイル時に計算できるもの)」は `const` です。

—

3. コードで体感する!具体的な挙動の違い

百聞は一見に如かず。実際にDartのコードを書いて、それぞれの挙動を見てみましょう。

void main() {
// 1. 実行時(Runtime)に決まる値 -> final を使う
// 現在時刻は、コードを書いている時点では絶対に分かりませんよね。実行時に決まります。
final DateTime now = DateTime.now();
print(‘現在時刻(final): $now’);

// 【エラーになる例】
// const DateTime nowConst = DateTime.now();
// ❌ コンパイルエラー!「DateTime.now()はコンパイル時に計算できないよ」とDartに怒られます。

// 2. コンパイル時(Compile-time)に決まる値 -> const を使う
// 円周率や固定の数値は、ビルドする前から確定しています。
const double pi = 3.141592;
const int maxRetryCount = 3;

print(‘円周率(const): $pi’);
print(‘最大リトライ回数(const): $maxRetryCount’);

// 3. コレクションにおける違い(ここが一番重要!)
// finalなリスト
final List finalColors = [‘red’, ‘green’];
finalColors.add(‘blue’); // OK! リストの中身(要素)は変更できます(再代入は不可)
// finalColors = [‘yellow’]; // ❌ NG! 変数自体を別のリストに置き換えることはできません。

// constなリスト
const List constColors = [‘red’, ‘green’];
// constColors.add(‘blue’); // ❌ 実行時エラー (Unsupported operation)!
// constで宣言されたデータは、中身も含めて完全に「イミュータブル(変更不可能な聖域)」になります。
}

ここがプロのポイント!

`const` は、単なる「変数の定数化」ではありません。メモリ上の同じデータを徹底的に共有(Canonicalization)するという強力な最適化をDart VMに行わせます。
例えば、アプリ内のあちこちに `const Text(‘Hello’)` があっても、Dartはメモリ上に1つだけそのオブジェクトを生成し、それを使い回します。これがFlutterアプリのパフォーマンスを極限まで引き上げる秘密なんです。

—

4. 初学者がやりりがちな「よくある文法エラーと罠」

最後に、開発現場やレビューで本当によく見かける罠をいくつかご紹介しておきますね。

罠1: 「finalだから中身も絶対に変更できない」という誤解

先ほどのコードでも触れましたが、`final` は「変数への再代入(=バインディングの変更)」を禁止するものであり、オブジェクトの内部状態の変更(ミュータビリティ)まで禁止するわけではありません。

final profile = {‘name’: ‘Alice’};
profile[‘name’] = ‘Bob’; // ⚠️ これはエラーになりません!マップの中身は変わります。

もし中身のデータも含めて一歩たりとも変更させたくない場合は、`const` を使うか、`.unmodifiable` などのイミュータブルなコレクションを組み合わせて使いましょう。

罠2: クラスのコンストラクタで `const` を忘れる

FlutterでUIを作るときによく見る `const SizedBox(height: 16)` ですが、もし自作のカスタムウィジェットでこの恩恵を受けたい場合は、コンストラクタに `const` をつける必要があります。

class MyButton extends StatelessWidget {
// コンストラクタに const をつけることで、親ウィジェット側で const MyButton() と呼べるようになる
const MyButton({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container();
}
}

ここを `const` にしておくと、Flutterの再描画(Rebuild)の際に「あ、このウィジェットは中身が変わってないから再構築をスキップしよう!」とフレームワークがサボってくれるため、アプリがキビキビ動くようになります。

—

まとめ

いかがでしたでしょうか?

  • 迷ったらまず `final` から検討する(安全で汎用的)。
  • 「ビルド時に値が確定し、メモリ効率を極限まで高めたい」「中身も完全フリーズさせたい」という場合は `const` を選ぶ。

この「評価タイミング」の軸さえ押さえておけば、もう変数宣言で迷うことはありません。
ここをクリアできれば、Dartのメモリモデルの基本はバッチリマスターできていますよ!

自信を持って、明日からのコーディングを楽しんでいきましょう!質問や疑問があれば、いつでも先輩に聞いてくださいね。

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