【Flutter】Dartにおけるinitializer list・イニシャライザについての解説

Flutter基礎

Flutterでコンストラクタを使っていてちょこちょこinitializer listというものが出てきてどういう役割か理解を深めたく記事にしました。

Dartにおけるinitializer listって何者か、イニシャライザってどんな役割を果たすのかなどを知りたい方の参考にあれば幸いです。

公式

Constructors
Everything about using constructors in Dart.

公式でinitializer listの説明に下記のような記載がありました。

Before the constructor body runs, you can initialize instance variables. Separate initializers with commas.

コンストラクタ本体が実行される前に、インスタンス変数を初期化できます。初期化子はカンマで区切ってください。

Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

ここでいうinitializer listは:以降の下記部分になります。

x = json['x']!, y = json['y']!

イニシャライザ

イニシャライザとは

そもそもイニシャライザとは、

  • コンストラクタ内でフィールドを初期化する仕組み

です。

英語でinitial(イニシャル)は「初期の」「最初の」という意味があります。

プログラミングでは基本的に、イニシャライズするというのは、「初期化すること」の理解でOKです。

initializer(イニシャライザ)とは、「初期化を行うもの・仕組み」で理解はOKですが、言語ごとでルールが違うのでそこは注意が必要です。

Dartにおけるイニシャライザ

Dartにおいてイニシャライザは、基本initializer listのことを指すことがほとんどです。複数の変数を初期化できるのでイニシャライザリスト(initializer list)と呼ばれています。

initializer list

initializer listとは

本題のinitializer listとは

  • コンストラクタ本体 {} が実行される前に、フィールドを初期化する部分

のことです。

Userクラスがあってコンストラクタを作った場合、

class User {
  final String name;
  final int age;

  User(String name, int age)
      : name = name,
        age = age;
}

:以降のname= name, age = age;の部分がinitializer listになります。

ここでは、フィールドと記載しますが、公式記載のインスタンス変数(instance variables)と同じ意味です。クラスの中にあるstaticでない変数は、フィールド・メンバ変数・インスタンス変数と呼ばれたりします。

イニシャライザがやっているのは、以下の通りです。

  • インスタンス変数(=フィールド)を初期化
  • static 変数は対象外

コンストラクタ本体

コンストラクタについては、下記の記事にまとめていますので、ご覧いただければと思います。

Before the constructor body runs, you can initialize instance variables

とコンストラクタ本体を実行する前に初期化する。と言うこのコンストラクタ本体とは、

  • オブジェクトが作られた後に実行したい処理を書く場所

になります。

先ほどのソースコードに追加すると、

class User {
  final String name;
  final int age;

  User(String name, int age)
      : name = name,
        age = age {
    // ここは「コンストラクタ本体」
    print('User created');
  }
}

{}内記載しているprint処理がコンストラクタ本体になります。

できることとしては、

  • 上記のようなログ出力
  • フィールドを使った処理(finalは不可)

などになります。

フィールドの値が確定した後に何か処理したい場合にここに記載します。

逆にinitializer listはフィールドがどんな状態かをコンストラクタ本体実行前に決めます

super()で親クラスのコンストラクタを呼ぶ

Flutterでは、initializer listでsuper()を使って親のコンストラクタを呼ぶことがよくあります。
親のAnimalクラスでnameを定義し、それをDogと言う子クラスで使用する場合

class Animal {
  final String name;

  Animal(String name) : name = name;
}

class Dog extends Animal {
  Dog(String name) : super(name);
}

super()とinitializer listに定義することで、子クラスのインスタンス生成時に、親クラスのコンストトラクタが呼ばれ、親クラスのフィールドを正しく初期化することができます。

上記の場合、super(name)とinitializer listに書くことで、

  • Dogクラスを実際にインスタンス生成する際に
  • まずAnimalクラスのコンストラクタが呼ばれ、
  • 親クラス(Animal)で定義されたフィールドnameが正しく初期化

と言う流れになります。

Flutterで以下のような定義が生成され、見たことある方も多いかなと思います。

class MyWidget extends StatelessWidget {
  const MyWidget({Key? key}) : super(key: key);
}

ここでいう、

: super(key: key);

もinitializer listでsuper親クラスのコンストラクタを読んでいます。

Flutterでは子Widgetで受け取ったkeyを親Widgetに渡して初期化します。こちらのkeyの部分については下記記事を参考にしてみるとより理解できると思います。

処理の流れ

initializer listに関わるsuperとコンストラクタ本体の処理を流れで言うと以下のようになります。

① メモリ上にオブジェクトを確保
② initializer list を実行(フィールドを確定)
③ super コンストラクタを実行
④ コンストラクタ本体 {} を実行

// 順番
initializer list
↓
super コンストラクタ
↓
コンストラクタ本体

initializer listで初期化を実行し、superコンストラクタを読んでいる場合、親のコンストラクタ内のフィールドを初期化し、最後にコンストラクタ本体を実行します。

this.の省略法

ここまでinitializer listについて解説してきましたが、Dartではinitializer listを省略することができ、Dart公式でのそちらを推奨しています。

prefer_initializing_formals
Learn about the prefer_initializing_formals linter rule.

先ほどの例で言うと、

class User {
  final String name;
  final int age;

  User(String name, int age)
      : name = name,
        age = age;
}

は、省略して、下記のように書くのがベターです。

class User {
  final String name;
  final int age;

  User(this.name, this.age);
}

Animalクラスの方でも

class Animal {
  final String name;

  Animal(String name) : name = name;
}

ではなく、

class Animal {
  final String name;

  Animal(this.name);
}

がよりコードがスッキリします。

initializer listの理解のために省略していない : name = name, age = age;の形で解説しましたが、実際には、this.をうまく用いてinitializer listを活用していきたいですね。

initializer listが使用される別のケース

最後にinitializer listが使用される別のケースを簡単に見ていきましょう。

assert をイニシャライザで使う

Flutterでよく使われるパターン

class Person {
  final int age;

  Person(this.age) : assert(age >= 0);
}
  • 不正な値を生成時にチェック
  • デバッグ時にバグを早く見つけられる

計算結果で初期化する

イニシャライザは 計算や変換をしながら初期化できる。

class Rectangle {
  final double width;
  final double height;
  final double area;

  Rectangle(this.width, this.height)
      : area = width * height;
}
  • areaコンストラクタ本体より前に
  • width * height の計算結果で初期化される

最後に

普段何気なく使っているコンストラクタやinitilalizer listについて理解を深めることで、新しい実装や既存のソースコードの読解がよりスムーズになるかなと思います。

“世界一流エンジニアの思考法”著者の牛尾さんもunderstandは最強と何かのインタビューで仰っていて、私自身もここにもっと注力したいと思います。

今回の記事がどなたかの参考になれば幸いです。

ではまた。

コメント

タイトルとURLをコピーしました