const, final, late, staticってどゆこと?Dartの変数修飾子をメモリから考える
こんにちは
変数を宣言する際に、何となくfinal
を使ったり、Analyzerに言われるがままconst
を使ったりしているけれど、その意味や、メモリ上でどう影響を与えるのかは意外と知らなかったりしませんか?
今回は、Dartの変数修飾子について、メモリ確保がどう変化するのかをまとめてみました。
はじめに: この記事のスコープ
この記事では、以下の4つの変数修飾子をつけることによって、メモリ上でなにが起きるのか整理します
4つの変数修飾子以外については紹介しません
const
final
late
static
対象読者
- Flutter, Dart を使った実装経験はあるものの、おまじないとして書くことが多い方
- メモリ効率や保守性の高い実装に興味が湧いてきている方
メモリってなに?
メモリ (Memory) とは、プログラムがデータを一時的に保存し、読み書きするための領域です
Dart や Flutter などのプログラムは、主に次のメモリ領域を使います
静的領域 (Static Area)
- プログラム起動時に確保され、終了まで保持される
- クラスの情報やstaticメンバーなどがこの領域に格納されます
ヒープ (Heap)
- 大きなメモリ領域
- インスタンスや動的なデータを保存
スタック (Stack)
- 小さな高速メモリ
- 関数の実行時に確保・終了時に解放
コンパイルってなに?
コンパイルとは、プログラムを機械が理解できる形に翻訳するプロセスです
コンパイル時 と プログラム開始時 の違い
項目 | コンパイル時 | プログラム開始時 |
---|---|---|
何をする? | プログラムを実行ファイルに翻訳 | メモリ(静的領域など)の初期化 |
例 |
const の値を決定 |
static 変数をメモリに配置 |
タイミング | プログラムを「作るとき」 | プログラムを「動かすとき」 |
クラスとは?
まず、クラスとは、オブジェクト指向プログラミングにおける設計図のようなものです。クラスはオブジェクトを生成するためのテンプレートを提供し、オブジェクトが持つべき属性(フィールド)や振る舞い(メソッド)を定義します。Dartにおいても、クラスはオブジェクトを生成するための枠組みであり、インスタンス化(オブジェクトを作成)することで実際のデータを持つことができます。
クラスをたい焼きの型とすると、オブジェクトはたい焼きのことで、たい焼きを作ることがインスタンス化、というイメージがわかりやすいらしいです!
// クラスの定義
class Taiyaki {
// フィールド(属性)
String flavor;
int size;
// コンストラクタ
Taiyaki(this.flavor, this.size);
// メソッド(振る舞い)
void describe() {
print('このたい焼きは$flavor味で、サイズは$sizeです。');
}
}
void main() {
// Taiyakiのインスタンスを作成
// いわゆるカッコをつけたらインスタンスができる
var taiyaki1 = Taiyaki('あんこ', 5);
var taiyaki2 = Taiyaki('カスタード', 6);
// メソッドを呼び出して、たい焼きの情報を表示
taiyaki1.describe(); // このたい焼きはあんこ味で、サイズは5です。
taiyaki2.describe(); // このたい焼きはカスタード味で、サイズは6です。
}
変数修飾子をつけるとなにが起きるの?
const
constは「絶対に変わらない値」として宣言され、プログラムが実行される前に値が確定します。具体的には、コンパイル時にその値が決まるため、アプリがビルドされる段階で固定され、実行中に変更することはできません。このため、constで定義された変数は静的領域に保存され、プログラムが終了するまで1つだけ保持されます。さらに、同じ値のconstはメモリを再利用するため、メモリ効率も非常に高いです。
例1: const
は同じ値なら、同じメモリ領域を再利用する
const a = 'hello';
const b = 'hello';
print(identical(a, b)); // true(同じメモリを共有している)
例2: const
は実行時に決まる値は持てない
int x = DateTime.now().second;
// const time = x; // エラー:constはコンパイル時に値が必要
まとめ
特徴 | 説明 |
---|---|
メモリ領域 | 静的領域(プログラム終了まで1つだけ) |
タイミング | コンパイル時 |
再利用 | 同じ値ならメモリ共有(効率◎) |
用途 | アプリ名・定数・設定値 |
final
finalは「一度だけ値を設定できる変数」ですが、constと異なる点は値の設定タイミングです。final変数の値は、プログラムが実行される時に決定され、初期化された後は変更できません。そのため、メモリ領域としてはヒープかスタックに配置され、constのように静的領域に置かれるわけではありません。実行時に初期化されるので、計算やデータベースのクエリ結果、ユーザーの入力など動的なデータを保持するのに向いています。
例えば、以下のようなコードでは、finalは動的な値でも問題なく使用できます。
final currentDateTime = DateTime.now();
print(currentDateTime);
まとめ
特徴 | 説明 |
---|---|
メモリ領域 | ヒープまたはスタック |
タイミング | 実行時 |
再利用 | 一度だけ設定され、再設定不可 |
用途 | 動的に決まる一度きりの値 |
late
lateは「遅延初期化」を意味し、変数の初期化を後回しにすることができます。これにより、最初に値が設定されるタイミングを遅らせることができ、無駄なメモリを節約できます。lateを使うと、変数の宣言時に初期化しなくてもコンパイラが警告を出さず、後で実際に初期化することができます。late変数は、実際に初期化されるまでメモリを消費しない点が特徴です。
たとえば、以下のようにlateを使うと、オブジェクトが必要になるまで初期化を遅らせることができます。
late String message;
void initializeMessage() {
message = 'Hello, Dart!';
}
まとめ
特徴 | 説明 |
---|---|
メモリ領域 | ヒープまたはスタック |
タイミング | 実行時(初期化が遅延) |
再利用 | 初期化後、再設定不可 |
用途 | 初期化が遅れてもよい場合(メモリ節約) |
static
staticは「クラス全体で共有される変数」で、インスタンスごとではなくクラス単位でメモリが確保されます。static変数は、クラスのメタデータがメモリにロードされるタイミングで1つだけ初期化され、全てのインスタンスで共有されます。そのため、同じクラスのインスタンスが何個あってもstatic変数は1つだけ使用され、メモリの効率が良いと言えます。
クラスのメモリ確保タイミング
クラス自体の情報(クラス名、メソッド、フィールドの型など)は、プログラムが起動した時点でメモリにロードされます。Dartのような言語では、JIT(Just-In-Time)コンパイルやAOT(Ahead-Of-Time)コンパイルの際に、クラスのメタデータがメモリに格納されます。つまり、クラスの定義情報は最初からメモリ上に存在しており、インスタンス化やstaticメンバーのアクセスに関わらず、クラス自体はすでにプログラム内に存在します。
一方で、クラスがインスタンス化された場合、そのインスタンスのフィールドやメソッドに対する実体(データ)がヒープメモリに確保されます。staticメンバーにアクセスする際には、インスタンスを生成しなくてもクラスのメタデータが利用され、メモリ上に保持されます。
staticを使ったカウンタの例
例えば、以下のようにstaticを使って、すべてのインスタンスで共有するカウンタを作成できます。
class Counter {
static int count = 0;
void increment() {
count++;
}
}
まとめ
特徴 | 説明 |
---|---|
メモリ領域 | 静的領域 |
タイミング | クラスがメモリにロードされる際 |
再利用 | クラス全体で共有 |
用途 | クラス単位で共有したいデータ |
さいごに
Dartの変数修飾子であるconst、final、late、staticは、それぞれメモリ管理やパフォーマンスに大きな影響を与えます。これらを適切に使い分けることで、効率的で保守性の高いコードを実現できます。
constはコンパイル時に決定される定数として、静的領域に保存され、同じ値ならメモリを共有して再利用します。固定の設定値や定数に最適です。
finalは実行時に値が決まるものの、その後変更できない変数を作るために使用します。1回だけ設定され、後から変更することはできません。
lateは遅延初期化を可能にし、最初に値を設定せず、後から初期化することができます。メモリを無駄に使わず、必要なタイミングで値をセットできます。
staticはクラス全体で共有される変数を定義します。インスタンスを作成せずともアクセスでき、クラスレベルでメモリを共有します。
もし、この記事に関して疑問や改善点があれば、ぜひコメントで教えてください!
Discussion