こんにちは!FlutterやDartを使った開発を楽しんでいますか?
今回は、Dartの性能を極限まで引き出すための鍵である「コンサイスな定数(`const`)」と「イミュータブルなコレクション」について、深く掘り下げてお話ししていきますね。
他のプログラミング言語からDartにやってくると、「`final`と`const`って何が違うの?」「リストに`const`をつけると何が嬉しいの?」と少し戸惑うかもしれません。
でも、ここをクリアすれば、Dartのメモリ効率やパフォーマンスの仕組みがスッキリ見えてきて、コードを書くのがもっと楽しくなりますよ。
さあ、Dartの内部の世界を一緒に覗いてみましょう!
—
1. 変数宣言の三兄弟:`var`、`final`、`const` の本質的な違い
Dartで変数を作るとき、私たちは主に3つのキーワードを使います。まずは、それぞれの立ち位置を整理しておきましょう。
- `var`(可変): 値を何度でも書き換えられる一般的な変数。
- `final`(実行時イミュータブル): 一度だけ値を代入できる変数。値が決まるのは「実行時(Runtime)」です。
- `const`(コンパイル時定数): コードを書いた瞬間、つまり「コンパイル時」に完全に値が確定し、二度と変更できない絶対的な定数。
イメージ図で表すと、こんな感じです。
[ 開発者の頭の中 (ソースコード) ]
│
▼ 【コンパイル(Dartのビルド)】
┌────────────────────────────────────────┐
│ const はここで「完全に評価」され、 │
│ メモリの定数領域に埋め込まれます。 │
└────────────────────────────────────────┘
│
▼ 【アプリの実行開始 (Runtime)】
┌────────────────────────────────────────┐
│ final はここで初めて値が代入され、 │
│ その後変更できなくなります。 │
└────────────────────────────────────────┘
「一度きりの代入」という意味では同じように見えますが、「いつその値が決まるのか(評価のタイミング)」が決定的な違いです。
—
2. `const` コレクションがもたらす圧倒的なパフォーマンスの秘密
さて、本題の「イミュータブル(変更不可能な)コレクション」についてです。
Dartでは、リストやマップ、セットの頭に `const` をつけることができます。
まずはコードを見てみましょう。
void main() {
// constを使ったリストの宣言
const colors =
// ⚠️ 以下の操作はすべてコンパイルエラーになります!
// colors.add(‘yellow’); // 要素の追加は不可
// colors[0] = ‘pink’; // 要素の書き換えも不可
}
「書き換えができないなら、`final`で作るのと何が違うの?」と思いますよね。ここがDartの真骨頂です。
実行時コストの「ゼロ化」
`const` で作られたコレクションは、アプリが起動した瞬間、すでにメモリ上の特定の場所に「一つのインスタンス」として存在しています。
もし、画面を再描画するたびに(例えばFlutterの `build` メソッド内で)以下のようなコードがあったとします。
// 良くない例:finalを使った場合
final colors = [‘red’, ‘green’, ‘blue’];
この場合、関数が呼ばれるたびに新しいリストのオブジェクトがメモリ上に新しく生成され、ガベージコレクタ(GC)に負担をかけます。
しかし、これを `const` にするとどうなるでしょう?
// 素晴らしい例:constを使った場合
const colors = [‘red’, ‘green’, ‘blue’];
コンパイラは賢いので、アプリ全体でこの `const` リストが何回使われようとも、メモリ上の同じアドレス(インスタンス)を使い回します(これを「カノニカライゼーション」と呼びます)。
つまり、実行時のメモリ割り当てコストが完全にゼロになるのです。
—
3. 「深い(Deep)不変性」:`const` は中身まで凍らせる
`const` の強力な特徴のもう一つは、「ディープ・イミュータブル(深層不変性)」です。
`const` をリストやマップの先頭につけると、その中に入っている要素の隅々まで、再帰的に「変更不可能」な定数としてコンパイル時に固定されます。
void main() {
// ネストされた構造もすべて const にする必要があります
const userInfo = {
‘name’: ‘Dart Wizard’,
‘skills’: [‘Dart’, ‘Flutter’, ‘VM’], // リストも const になる
};
// ⚠️ 実行時ではなく、コンパイルエラーとして弾かれます
// userInfo[‘skills’].add(‘C++’);
}
ここで注意したいのが、「`const` をつけるときは、中身の要素もすべて `const` で評価できなければならない」というルールです。
よくある文法エラー:実行時の値を `const` に入れようとするミス
初学者が非常につまずきやすいポイントがここです。以下のコードを見てください。
void main() {
String userInput = ‘gold’;
// ❌ 以下のコードはコンパイルエラーになります!
// const preciousColors = [‘red’, ‘green’, userInput];
}
エラーメッセージ(意訳): 「`userInput` はコンパイル時定数ではありません。」
なぜエラーになるのでしょうか?
`userInput` という変数は、アプリが「実行されてから」値が決まりますよね。一方、`const` は「アプリがビルドされる(コンパイルされる)時」に値が確定していなければなりません。
「未来の値(実行時にならないとわからない値)を、過去の時点(コンパイル時)で確定させようとした」ため、Dartのコンパイラが怒っているのです。
この場合は、以下のように `final` を使うのが正解です。
void main() {
String userInput = ‘gold’;
// ⭕ 実行時に値が決まるものは final を使います
final preciousColors = [‘red’, ‘green’, userInput];
}
—
4. Flutter開発における `const` の絶大な恩恵
もしあなたがFlutterを使っているなら、`const` をマスターすることは避けて通れません。なぜなら、FlutterのUI構築において `const` は不要な再描画(リビルド)を防ぐ最強の最適化ツールだからです。
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return const Column(
children: [
Text(‘こんにちは!’),
SizedBox(height: 16),
Text(‘Dartの世界へようこそ’),
],
);
}
}
Flutterフレームワークは、親Widgetがリビルドされたとき、子Widgetが `const` で作られていれば、「あ、このWidgetは中身が絶対に変わらないから、比較すらスキップしてそのまま使い回そう!」と判断します。
これにより、UIのレンダリングパフォーマンスが劇的に向上します。
—
まとめ:ここをクリアすればDartは怖くない!
今回は、Dartのコンパイル時定数(`const`)とイミュータブルなコレクションについて解説しました。
- `final`:実行時に一度だけ値が決まる(Runtime)。
- `const`:コンパイル時に値が完全に決まり、メモリ効率が最大化される(Compile-time)。
- `const` コレクションは、中身の要素も含めて「完全に不変」になり、アプリのパフォーマンスを底上げする。
- 実行時に決まる値を `const` の中に入れることはできない。
ここをしっかりと理解できれば、あなたの書くDartコードは、無駄なメモリ消費のない、美しく高速なものに生まれ変わります。
ここをクリアすれば、Dartの基本はバッチリマスターできますよ!自信を持って次のステップへ進んでいきましょう。
それでは、また次回の記事でお会いしましょう!