Dartのコンストラクタの書き方

最近Flutterの勉強を始めたのですが、Dartのコンストラクタの書き方が今まで触ってきた言語(主にKotlin、Java)に比べて少しとっつきにくく感じたので、基本的な部分を整理するために書いている記事です。

内容としては、主に以下のページから関連する箇所をピックアップしたものになります。

dart.dev



通常のコンストラク

通常のコンストラクタの書き方自体は他の言語と似ています。

class Point {
  num x, y;

  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}


コンストラクタのシンタックスシュガー

Dartのコンストラクタにはシンタックスシュガーがあります。
フィールドをコンストラクタのパラメータに記述することで、パラメータの宣言とフィールドの初期化処理を同時に行うことができます

class Point {
  num x, y;

  Point(this.x, this.y);
}


上記のような場合、フィールドにfinal修飾子を付けて再代入不可にするケースも多いかと思いますが、その場合は通常のコンストラクタの書き方ではコンパイルエラーになります

class Point {
  final num x, y;

  // OK
  Point(this.x, this.y);

  // NG
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}


どうやら、finalを付けるとフィールドのsetterが定義されないので、初期化のタイミングの問題でエラーになってしまうようです。
そのため、多くの場合はシンタックスシュガーの書き方を使うことになると思います。

Error: The setter 'x' isn't defined for the class 'Point'.
Error: The setter 'y' isn't defined for the class 'Point'.


名前付きコンストラク

Dartでは、コンストラクタ名.名前の形で名前付きのコンストラクタが作成できます。
名前付きコンストラクタはパラメータを指定することも可能です。

class Point {
  num x, y;

  Point(this.x, this.y);

  // 原点を表すPointインスタンスを生成するためのコンストラクタ
  Point.origin() {
    x = 0;
    y = 0;
  }

  // x軸上の任意の1点を表すPointインスタンスを生成するためのコンストラクタ
  Point.alongXAxis(num value) {
    x = value;
    y = 0;
  }
}


私が今まで触ってきた言語では見たことがありませんでしたが、これは便利だと感じました。


コンストラクタのリダイレクト

Dartでは、コンストラクタの後ろに:を付け、thisキーワードを使うことによって同一クラス内の別のコンストラクタに処理を移譲できます。

先ほどのようにフィールドにfinal修飾子を付けて再代入不可にした場合、リダイレクトを行わない書き方では同様の理由でコンパイルエラーになります

class Point {
  final num x, y;

  Point(this.x, this.y);

  // OK
  Point.origin() : this(0, 0);

  // NG
  Point.origin() {
    x = 0;
    y = 0;
  }
}


Initializer list

個人的にはこの書き方が一番とっつきにくく、FlutterのDemoアプリのソースコードを読んで最初に困惑した部分でした。
Initializer listは親クラスのコンストラクタの呼び出しやフィールドの初期化処理をワンライナーで行う記法で、コンストラクタの後ろに:を付け、,で区切る形で記述します。

Flutterでは、EdgeInsetsクラス*1などでこの記述が使われています。
先ほどの名前付きコンストラクタと組み合わせています。

// ※改行は問題ない
EdgeInsets.all(double value)
  : left = value,
    top = value,
    right = value,
    bottom = value;


親クラスのコンストラクタを呼び出す場合はこの書き方でないと無理なようなので、Widgetの継承を多用して開発を行うFlutterのソースコードではよく見る記法になると思います。


名前付きパラメータ

最後はコンストラクタ自体ではなく、コンストラクタに指定するパラメータの話です。
したがって、この内容はDartの関数・メソッド・コンストラクタ共通の話です。

Dartの関数は必須パラメータと任意パラメータを指定することが可能です。
任意パラメータには{ }で括る名前付きパラメータと[ ]で括る順序付きパラメータの2種類があるようですが、ここでは前者だけを扱います。
(後者は使い勝手が悪そうなので、ほとんど使われないのではというのもあり…)

名前付きパラメータを使うと、コンストラクタ生成時にラベル付きで引数を代入できるようになります。
Flutterでは、Textクラスなどでこの記述が使われています。

Text(
  this.data, {
  Key key,
  this.style,
  this.strutStyle,
  this.textAlign,
  this.textDirection,
  this.locale,
  this.softWrap,
  this.overflow,
  this.textScaleFactor,
  this.maxLines,
  this.semanticsLabel,
  this.textWidthBasis,
})


Textクラスでは、dataだけが必須パラメータ、その他は任意パラメータとなっています。
テキストのフォントサイズを20pxにする場合は以下のように記述します。

Text(
  'sample', // 必須パラメータ
  style: TextStyle(fontSize: 20.0),
)


個人的には、Kotlinに慣れていると必須パラメータであっても名前を付けたいと思ってしまいます。
その場合は、該当の任意パラメータの前に@requiredアノテーションを付けることで必須パラメータにできるようです。


今回は以上となります。
Dartには他にもFactoryコンストラクタ、定数コンストラクタといったコンストラクタがあるようですが、それらについては必要になったタイミングで改めて詳細を追ってみたいと思います。