忙しい人のためのまとめ
constもfinalも再代入でエラーになるが……
- finalは「器」の固定:中身(プロパティ)の変更は許容される
final A = SampleClass();
A.variable = 5; //通る
A.variable = 7; //これも通る
A = SampleClass(); //error
- constは「中身」まで凍結:オブジェクトそのものが不変(イミュータブル)になる
const A = SampleClass();
A.variable = 5; //error
A.variable = 7; //error
A = SampleClass(); //もちろんerror
目次
前置き
この記事の目的
1. 決定的な違いは「いつ」決まるか
2. constとfinalを使う理由
3. インスタンスを書き換えられる理由・書き換えられない理由
4. 結局どっちを使えばいい?
まとめ
あとがき
前置き
Flutter歴一年のペーペーですが、備忘録としていろいろ書いていけたらと思います。極力調べて記述しておりますが、間違った点などありましたらご教授くだされば幸いです。
この記事の目的
-
finalとconstの違いについて、仕組みから整理する - 初学者の「どっちを使えばいいの?」「なぜ使うの?」を解消する
1. 決定的な違いは「いつ」決まるか
最大の相違点は、値が確定するタイミングにあります。
-
final:実行時(Runtime)に決まる
アプリを動かして、そのコードに到達した瞬間に値が確定します。そのため、DateTime.now()のように「動かしてみないとわからない値」を代入できます。 -
const:コンパイル時に決まる
アプリをビルドして配布する時点で、すでに値が決まっている必要があります。まさに「真の定数」です。
2. constとfinalを使う理由
const を使うと、Dartはメモリを賢く節約してくれます。これを「正規化(Canonicalization)」と呼びます。
// finalの場合
final f1 = [1, 2];
final f2 = [1, 2];
print(identical(f1, f2)); // false(別々のメモリ領域)
// constの場合
const c1 = [1, 2];
const c2 = [1, 2];
print(identical(c1, c2)); // true(同じメモリ領域を指している!)
const で宣言された値は、中身が同じであればメモリ上の同じ場所を使い回します。Flutterで const ウィジェットが推奨されるのは、リビルドの際、この仕組みによって「あ、これさっきと同じやつだから作り直さなくていいや」と判断できるため、動作が軽くなるからです。
え? じゃあfinalを使う意味はなんなの?
自分が疑問を持ったのはここでした。constが動作を軽量化するのは分かったけど、それだとfinalの存在理由がわからない。これに対しての答えですが、finalを使う理由は主に「人間(開発者)のミスを防ぎ、コードの意図を明確にする」という設計上の利点にあります。
使用例
-
意図しない再代入によるバグを防ぎたいとき
//varの場合 var userId = "user_123"; // ... (100行の処理) ... userId = "user_456"; // どこかでうっかり書き換えてしまった! //finalの場合 final userId = "user_123"; // ... (100行の処理) ... userId = "user_456"; // errrorが出るので気付ける! -
知らないうちに書きかわらないことを保証
finalを多用して不変なオブジェクトを増やすことで、「一度作成されたデータは、どこに渡されても内容が保証される」という安心感が生まれます。これは、特に非同期処理や複数の関数をまたぐ処理において、デバッグの難易度を下げてくれます。 -
クラスの安全性を高めたいとき
class UserCard extends StatelessWidget { final String name; // 外部から注入され、その後は不変 UserCard({required this.name}); @override Widget build(BuildContext context) { return Text(name); } }
3. インスタンスを書き換えられる理由・書き換えられない理由
コード例をもう少し深掘りして、今度はfinalとconstの仕組みからプロパティを変更できる理由・できない理由について解説していきます。
finalは「浅い不変性」
final は変数が指し示す「住所(メモリ上のアドレス)」を固定します。しかし、その住所にある家の壁紙を塗り替える(プロパティを書き換える)ことまでは制限しません。
// finalの場合
final A1 = SampleClass();
final A2 = SampleClass();
print(identical(A1, A2)); // false(別々のメモリ領域)
A1.variable = 1; //プロパティの書き換えは可能
A2.variable = 2;
print(A1.variable); //1 別インスタンスなのでA2.variableの書き換えはA1.variableに影響しないため、書き換えられる
A1 = SampleClass(); //!error これは「住所(アドレス)」自体の書き換えに相当するためできない
constは「深い不変性」
const は、そのインスタンス自体を完全に凍結します。2. constとfinalを使う理由にある通り、constは複数の変数が同じインスタンスを参照するため、ここでプロパティの書き換えを許さずにエラーをだすことで、予期せぬところで値の変更が生じないようになっています。
さらに、自作クラスで const を使うには、クラス側に以下の準備が必要です。
- コンストラクタに
constが付いていること - すべてのフィールドが
finalであること
class SampleClass {
final int variable; // フィールドもfinalでないとconstにできない
const SampleClass(this.variable);
}
これにより、コンストラクタが実行された後にインスタンスのプロパティが書きかわらないことを保証できます。
4. 結局どっちを使えばいい?
迷ったら、私は以下の優先順位で検討するようにしています。
-
constにできるか?- コードを書いている時点で値が決まっている(固定の文字、色、数値など)。
-
できなければ
finalにする- 実行後に決まるが、二度と書き換えない(APIのレスポンス、ユーザー名など)。
-
どうしても後で書き換えるなら
var/型名- カウンターの数値や、状態管理で変化するフラグなど。
まとめ
final は「一度決めたら変えないよ(でも中身は触るかも)」という意思表示、const は「これはアプリのどこで使っても永遠に同じだよ」という、より強い宣言です。
適切に使い分けることで、バグの少ない、かつFlutterのパフォーマンスを最大限に引き出すコードを書くことができます。
あとがき
自分がflutterを始めたての頃、linterなどにconstをつけろ!と怒られ、別に動くからいいじゃない、と思っていたりしましたが、constをつけることでアプリの動作を軽くするという目的があることを知り、納得できました。
この記事が同じような誰かの糧となれば幸いです。