こんにちは!FlutterやDartを使った開発を楽しんでいますか?
他のプログラミング言語からDartの世界に飛び込んできたとき、多くの人が「`var`、`final`、`const`って、結局どう使い分ければいいの?」という疑問にぶつかりますよね。
特に`final`というキーワードは、単なる「変数の再代入禁止(一度入れたら値を変えられない)」というルール以上の、DartやFlutterにおける堅牢なアプリケーション設計の心臓部です。ここをクリアすれば、あなたの書くコードの安全性とパフォーマンスは劇的に跳ね上がりますよ。
今回は、表面的な使い方だけでなく、「なぜ`final`を使うのか」「Flutterのウィジェットツリーにどう影響するのか」という本質的な部分まで、優しく紐解いていきましょう!
—
1. `final` の基本:単なる「再代入禁止」ではない
まずは基本のおさらいです。`final` を使って宣言した変数には、一度だけ値を代入することができます。二度目以降に値を代入しようとすると、Dartのコンパイラが怒り出し、コンパイルエラーになります。
void main() {
// finalを使った変数の宣言
final String appName = ‘Flutter Masterclass’;
// これはOK(最初の代入)
print(appName);
// ❌ コンパイルエラー!
// appName = ‘New App Name’;
// 「Error: Can’t assign to the final variable ‘appName’.」
}
ここで重要なのは、「変数の箱(入れ物)をテープで封印する」イメージを持つことです。`appName` という箱には、最初に `Flutter Masterclass` という文字列オブジェクトを入れたら、もう別の箱に入れ替えることはできません。
よくある誤解:「中身まで変わらない」わけではない
初心者が一番ハマりやすいポイントがここです。`final` は「変数(参照)の再代入」を禁止しているのであって、「オブジェクトの中身の変更」まで禁止しているわけではありません。
例えば、`List`(リスト)を `final` で宣言したケースを見てみましょう。
void main() {
final List
// 1. リストの中身(要素)を追加・変更する
techStack.add(‘Riverpod’); // これはエラーになりません!
print(techStack); // 出力: [Dart, Flutter, Riverpod]
// 2. 別のリスト自体を再代入する
// techStack = [‘Java’, ‘Spring’]; // ❌ これはコンパイルエラーになります!
}
なぜ中身の追加ができるのでしょうか?
Dartのメモリ上では、`techStack` という変数は「リストが置いてあるメモリ上の住所(参照)」を指しています。`final` が縛っているのは「別の住所への書き換え」だけなので、住所の先にある家の中(リストの要素)をリフォームすることは許されているのです。
オブジェクト自体を完全に不変(イミュータブル)にしたい場合は、コレクションなら `const` を使うか、`unmodifiable` なリストにする配慮が必要です。この「参照の不変性」と「オブジェクトの不変性」の区別は、中級者への第一歩ですよ!
—
2. なぜ `final` を使うのか?(設計思想の観点)
「じゃあ、全部 `var` で作って、変えたくないものは変えなければいいじゃん」と思ったそこのあなた。非常に鋭い視点ですが、実務の現場ではそれは悪手になってしまいます。
私たちがコードを書くとき、脳のメモリ(認知負荷)は常に有限です。`var` で宣言された変数は、「この後、どこかで値が変わるかもしれない」という可能性を常に孕(はら)んでいます。数千行もある大規模なコードベースでこれをやると、バグの追跡が極めて困難になります。
`final` を積極的に採用するメリットは主に3つあります。
1. 意図しないバグの予防: 変数がどこかで書き換わっていないか、コードを上から下まで追いかける必要がなくなります。
2. コードの意図が明確になる: 「この値は、この処理の中で一度決まったら二度と変わらないんだな」という設計者の意図が、一目で読み手に伝わります。
3. 安全な並行処理(Isolate)の土台: Dartはシングルスレッドですが、別スレッドである「Isolate」を使って重い処理を並行実行できます。データがイミュータブル(変更不能)であれば、スレッド間でデータを安全に共有しやすくなります。
—
3. Flutterのウィジェットツリーにおける「再描画最適化」への影響
さて、ここからが本題です。Flutterにおいて、`final` は単なる文法の枠を超え、パフォーマンス最適化の強力な武器になります。
FlutterのUIは「ウィジェットのツリー構造」でできています。画面の状態(State)が変化すると、Flutterはウィジェットツリーを作り直(再描画)します。その際、フレームワークは「このウィジェットは前回のフレームから変わっていないから、再利用(スキップ)しよう」と判断したくなります。
ここで威力を発揮するのが、コンストラクタにおける `final` フィールドです。
import ‘package:flutter/material.dart’;
// ステートレスウィジェットの典型例
class UserProfileCard extends StatelessWidget {
// コンストラクタで受け取る値は、すべて final にする!
final String userName;
final String userEmail;
final VoidCallback onTap;
const UserProfileCard({
super.key,
required this.userName,
required this.userEmail,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(userName, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text(userEmail),
],
),
),
);
}
}
なぜこれが最適化につながるのか?
1. イミュータブルな設計の強制: `UserProfileCard` が持つすべてのプロパティが `final` であるため、このウィジェットは「生成された後に内部の状態が勝手に変わることは絶対にない」と保証されます。
2. Flutterエンジンによるキャッシュと const 活用: 親ウィジェットが再描画されたとき、もし渡している `userName` や `onTap` が変わっていなければ、Flutterは「この `UserProfileCard` は中身が一切変わらないから、再構築(buildメソッドの実行)を丸ごとスキップしよう!」と判断できます。
3. const コンストラクタの恩恵: フィールドがすべて `final` であるおかげで、このクラスには `const` コンストラクタを付与できます。これにより、ウィジェット自体をコンパイル時定数としてメモリ上にキャッシュし、無駄なオブジェクト生成のガベージコレクション(GC)負荷をゼロに近づけることができます。
もしフィールドに `var` を使っていたら、ウィジェットの不変性が担保できないため、Flutterは安全のために毎回ウィジェットを再構築せざるを得なくなり、アプリのパフォーマンス(フレームレート)がじわじわと低下していく原因になります。
—
まとめ:ここをクリアすれば、Dartの基本はバッチリマスター!
今回は、`final` 変数の再代入禁止という基本から、イミュータブルな状態管理、そしてFlutterのパフォーマンス最適化への波及効果まで深く掘り下げて解説しました。
- `final` は変数の「参照(箱)」の再代入を禁止するものである。
- オブジェクトの中身(リストの要素など)まで変更不能になるわけではない点に注意する。
- Flutterでは、ウィジェットのフィールドを `final` にしてイミュータブルに保つことが、無駄な再描画を防ぎ、滑らかなUIを実現するカギとなる。
「とりあえず動くから `var` でいいや」ではなく、「ここは絶対に変わらないから `final` にしよう」と意識してコードを書くようになった瞬間から、あなたのコードはプロフェッショナルのそれへと昇華します。
ここをクリアしたあなたなら、Dartのオブジェクト指向やFlutterのアーキテクチャ設計もスムーズに理解できるはずです。自信を持って、次のステップに進んでいきましょう!