Dartのコンストラクタについて

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Dartライブラリの中で見慣れないコンストラクタを目にしたので「Language Specification」で調べてみました。

※執筆時現在「Language Specification」はドラフトである為、以後の更新によって記事内容との差異が発生する可能性があります。ご注意ください。

Dartには、「生成的コンストラクタ」「ファクトリ」「定数コンストラクタ」と三種類のコンストラクタが用意されているようです。さらに生成的コンストラクタには「Automatic field initialization」「Named Constructors」「Redirecting Constructors」「Initializer Lists」といった特徴が備わっていることがわかりました。

Generative Constructors(生成的コンストラクタ)

一般的なコンストラクタです。「new」キーワードでインスタンスを生成する際に処理されます。

class Dog {
  var name;
  Dog() {
    this.name = 'Anonymous';
  }
}
main() {
  var dog = new Dog();
  print(dog.name); // Anonymous
}

引数を取るコンストラクタは以下の通りです。

class Dog {
  var name;
  Dog(name) {
    this.name = name;
  }
}
main() {
  var dog = new Dog('Anonymous');
  print(dog.name); // Anonymous
}

Automatic field initialization

引数を取るコンストラクタで引数の名称を「this.フィールド名」とすると代入処理を記述せずにフィールドを初期化することができます。

class Dog {
  var name;
  Dog(this.name);
}
main() {
  var dog = new Dog('Anonymous');
  print(dog.name); // Anonymous
}

Named Constructors

Dartでは「コンストラクタ名.任意名称」と記述することで複数のコンストラクタを定義することが可能です。
Javaに見られる「コンストラクタのオーバーロード」の代替として利用することができます。

class Dog {
  var name;
  Dog() {
    this.name = 'Anonymous';
  }
  Dog.name(this.name);
}
main() {
  var dog = new Dog.name('Pochi');
  print(dog.name); // Pochi
}

Redirecting Constructors

別のコンストラクタへ処理をリダイレクトすることが可能です。コンストラクタの後に、コロンに続けてリダイレクト先のコンストラクタを記述します。

class Dog {
  var name;
  Dog() : this.anonymous();
  Dog.anonymous() {
    this.name = 'Anonymous';
  }
  Dog.name(this.name);
}
main() {
  var dog = new Dog();
  print(dog.name); // Anonymous
}

リダイレクト元のコンストラクタに処理(ボディ)を持たせることもできます。その際はまずリダイレクト先のコンストラクタが処理され、その後に元コンストラクタのボディが処理されます。

class Dog {
  var name;
  var age;
  Dog() : this.anonymous() {
    this.age = 0;
  }
  Dog.anonymous() {
    this.name = 'Anonymous';
  }
  Dog.name(this.name);
}
main() {
  var dog = new Dog();
  print(dog.name); // Anonymous
  print(dog.age); // 0
}

Initializer Lists

コンストラクタの後に、コロンに続けてフィールドの初期化処理を記述します。複数フィールドの初期化時は代入処理をカンマ区切りでリストアップします。

class Dog {
  var name;
  var age;
  Dog() : this.anonymous();
  Dog.anonymous() : this.name = 'Anonymous',
                    this.age = 0;
  Dog.name(this.name);
}
main() {
  var dog = new Dog();
  print(dog.name); // Anonymous
}

以下は親クラスのコンストラクタ呼び出しです。「親クラスのイニシャライザリストを呼び出す」という意味でイニシャライザリストに含まれているようです。

class Dog {
  var name;
  var age;
  Dog() : this.anonymous();
  Dog.anonymous() : this.name = 'Anonymous',
                    this.age = 0;
  Dog.nameAge(name, age) : this.name = name,
                           this.age = age;
}

class Pochi extends Dog {
  Pochi() : super.nameAge('Pochi', 5);
}
main() {
  var pochi = new Pochi();
  print(pochi.name); // Pochi
  print(pochi.age); // 5
}

注意しなければならないのは、「コンストラクタのボディ内で親クラスのコンストラクタを呼び出すことはできない」ということです。
継承したクラスを以下のように記述するとエラーとなります。

class Pochi extends Dog {
  Pochi() {
    super.nameAge('Pochi', 5);
  }
}

Javaなどでは頻繁に用いる書式なので戸惑いやすいポイントかもしれません。

Factories(ファクトリ)

factory」キーワードをコンストラクタに指定するとそのコンストラクタはファクトリコンストラクタとなります。ファクトリコンストラクタではインスタンスが生成されない為、そのボディ内でインスタンスを生成し、返してあげる必要があります。

class Dog {
  var name;
  Dog() : this.anonymous();
  Dog.anonymous() : this.name = 'Anonymous';
  Dog.name(this.name);
}

class Pochi extends Dog {
  static var _instance;
  factory Pochi() {
    if (_instance == null) {
      _instance = new Pochi._internal();
    }
    return _instance;
  }
  Pochi._internal() : super.name('Pochi');
}
main() {
  var pochi1 = new Pochi();
  var pochi2 = new Pochi();
  print(pochi1 == pochi2); // true
}

上記ではシングルトン風にクラスを定義してみましたが、「getInstance」メソッドでのシングルトン実装に慣れてしまっていると「new」したインスタンスが同一というのに戸惑ってしまいますね。(^^;

Constant Constructors(定数コンストラクタ)

定数コンストラクタです。コンパイル時に定数オブジェクトをインスタンス化したい場合に利用するそうです。それによりDartライブラリと同じスコープに定数オブジェクトを用意することができます。定数オブジェクトクラスのフィールドは「final」とし、コンストラクタには「const」キーワードを付け、「const」キーワードでインスタンスを生成します。

class Dog {
  final name;
  const Dog() : this.anonymous();
  const Dog.anonymous() : this.name = 'Anonymous';
  const Dog.name(this.name);
}
final dog = const Dog();
final pochi = const Dog.name('Pochi');

main() {
  print(dog.name); // Anonymous
  print(pochi.name); // Pochi
}

エントリーポイントである「main」関数のスコープ外でインスタンス生成が行えていることがわかります。雰囲気としてはJavaScriptでグローバル変数を定義しているような感じでしょうか。

まとめ

コンストラクタにボディを記述しなければ「リダイレクト」「親コンストラクタ呼び出し」「フィールド初期化」しか行えなくなり、コンストラクタの役割りを「内容の初期化」に限定することが出来ます。私個人としては「コンストラクタのボディには極力処理を書かない」というのがDart流なのかなと感じました。(^^)

リンク