こんにちは!Dartの深淵へようこそ。
今日は、Dartを学び始めたばかりの方が必ずと言っていいほど直面する、そして中級者になっても時折「あれ?」と手が止まってしまう『const』の境界線についてお話しします。
「`final`と`const`って何が違うの?」「なぜこのコードはエラーになるの?」
そんな疑問を抱えているなら、この記事はまさにあなたのためのものです。Dart VMの内部で何が起きているのかを少しだけ覗き見しながら、本質的な違いをマスターしていきましょう。
ここをクリアすれば、Dartの基本はバッチリマスターできますよ。
—
1. 似ているようで全く違う「3つの宣言」
まずは基本の整理からです。Dartには変数を宣言する方法が主に3つありますが、それぞれ「いつ値が決まるか」という時間軸が異なります。
| キーワード | 再代入 | 値が確定するタイミング | 特徴 |
| :— | :— | :— | :— |
| var | 可能 | 実行時(Runtime) | 一般的な変数。型推論が効く。 |
| final | 不可 | 実行時(Runtime) | 一度決めたら変えられない「読み取り専用」。 |
| const | 不可 | コンパイル時 | 実行前から値が決まっている「不変の定数」。 |
ここで最も重要なのは、`const`は「プログラムが動き出す前(コンパイル時)」に、その値が完全に確定していなければならないという点です。
—
2. 「const」になれるもの、なれないものの境界線
「コンパイル時に確定する」とはどういうことでしょうか? 具体的なコードでその境界線を見てみましょう。
〇 constになれる(コンパイル時定数)
リテラル(数値、文字列、真偽値)や、それらを組み合わせた計算式はOKです。
// 数値や文字列の計算(コンパイラが計算できる)
const speedOfLight = 299792458;
const greeting = ‘Hello, ‘ + ‘Dart!’;
// 他のconstを使った計算
const double pi = 3.14159;
const double radius = 10.0;
const double area = pi radius radius; // 全てconstなのでOK
× constになれない(実行時まで不明なもの)
「実行してみないとわからない値」は、絶対に`const`にはできません。
// 現在時刻は実行するたびに変わるため、コンパイル時には決められません
// const now = DateTime.now(); // ❌ コンパイルエラー!
final now = DateTime.now(); // ✅ finalならOK
// 関数の戻り値も、基本的には実行時に決まるのでNG
int getNumber() => 42;
// const val = getNumber(); // ❌ コンパイルエラー!
—
3. 複雑なオブジェクトを定数化する「工夫」
Dartの面白いところは、リスト(List)や自作のクラス(Class)さえも`const`にできる点です。ただし、そこには厳格なルールがあります。
constコンストラクタの魔法
自作のクラスを`const`として扱うには、「constコンストラクタ」を定義する必要があります。
class User {
final String name;
final int age;
// 1. 全てのフィールドが「final」であること
// 2. コンストラクタに「const」がついていること
const User(this.name, this.age);
}
void main() {
// これで、このオブジェクトは「不変の定数」としてメモリに配置されます
const admin = User(‘Alice’, 30);
}
コレクションのconst
リストやマップも`const`にできますが、中身もすべて`const`でなければなりません。
// ✅ リスト全体を定数にする
const list = [1, 2, 3];
// ❌ 中身に変数やfinalが混じっているとダメ
final x = 10;
// const badList = [x, 20]; // ❌ xはconstではないのでエラー
—
4. なぜ「const」をこれほどまでに強調するのか?(プロの視点)
ここからは、Dart VMを知り尽くしたアーキテクトとしての視点をお伝えします。なぜ単なる「書き換え禁止」以上の意味があるのでしょうか。
それは、「カノニカル化(Canonicalization)」という仕組みがあるからです。
Dartコンパイラは、プログラムの中に全く同じ内容の`const`オブジェクトを見つけると、それらをメモリ上の全く同じ場所(アドレス)に集約します。
void main() {
const a = [1, 2];
const b = [1, 2];
// aとbは、メモリ上の全く同じ実体を参照しています!
print(identical(a, b)); // 結果: true
}
これがFlutterで非常に重要な役割を果たします。
FlutterのWidgetに`const`をつけると、画面が更新(リビルド)される際、フレームワークは「あ、これは前と同じ定数だね。作り直す必要はないな」と判断し、圧倒的なパフォーマンス向上をもたらします。
「constはメモリを節約し、アプリを高速化する魔法のキーワード」だと覚えておいてください。
—
5. よくある「落とし穴」と解決策
初心者が最もハマりやすいのが、「constの伝播」です。
class Point {
final int x;
const Point(this.x);
}
void main() {
// エラー例
// const point = Point(DateTime.now().millisecond);
// ↑ Point自体はconst対応でも、引数が「実行時」の値なのでアウトです。
}
解決のヒント:
- 「この値はプログラムを書いている今、決まっているか?」を自問自答してください。
- 決まっていないなら、迷わず `final` を使いましょう。
- `final` は「一度だけ代入できる変数」、`const` は「永遠に変わらない値」です。
—
まとめ:Dartを掌握するための第一歩
1. var は自由、final は一度きりの約束、const は不変の真理。
2. const にできるのは、コンパイル時に値が確定するものだけ。
3. 自作クラスを const にするには、全てのフィールドを `final` にして `const` コンストラクタを作る。
4. const を使うと、Dart VMがメモリを最適化し、アプリがサクサク動くようになる。
最初はエラーに戸惑うこともあるでしょう。でも、それはコンパイラが「もっと効率的なコードが書けるよ!」と教えてくれている合図です。
この境界線を理解すれば、あなたの書くDartコードはより堅牢で、プロフェッショナルなものへと進化します。一歩ずつ、楽しみながらマスターしていきましょうね。応援しています!