🎯

dartのコンストラクタでデフォルト値を与える方法が難しい

2022/10/23に公開

dartのコンストラクタ難しすぎ問題。

コンストラクタで、パラメータを省略したり、デフォルト値を与えたりする場合に書き方がわからずに混乱したのでまとめます。

この記事を書いているときのdartのバージョンは2.18です。

コンストラクタのおさらい

他の言語から来ると、コンストラクタでフィールドの初期化で以下のようなものを書いてしまいますがこれは動かなくて面食らいます。

class Foo {
  final String a;
  // Error: Final field 'a' is not initialized.
  //   final String a;
  
  Foo(String a) {
    this.a = a;
    // Error: The setter 'a' isn't defined for the class 'Foo'.
    //  - 'Foo' is from 'package:dartpad_sample/main.dart' ('lib/main.dart').
    //    this.a = a;
    //         ^
  }
}

dartではフィールドはコンストラクタボディの手前で初期化されていなければなりません。
ではどう書けばいいかというと、Initializer listを使い、

class Foo {
  final String a;
  Foo(String a) : this.a = a;
}

と書きます。コンストラクタの後ろにある:以降がInitializer listでコンストラクタより先に実行されます。
フィールドが2つある場合は,で区切ります。

class Foo {
  final String a;
  final String b;

  Foo(String a)
      : this.a = a,
        this.b = b;
}

でも毎回これを書くのは面倒なので糖衣構文が用意されていて、

class Foo {
  final String a;
  final String b;
  Foo(this.a, this.b);
}

thisを使って書くことができます。これは一つ前のサンプルコードと同じ意味になります。
これはよく見る書き方で、これしか知らない人も多いと思いますが実はこういう仕組みになっています。

名前付きにするとこう

class Foo {
  final String a;
  final String b;
  Foo({
    required this.a,
    required this.b,
  });
}

Flutterではよくみるやつ。

一部だけ名前付きにする

名前付きのパラメータと、名前がつかないパラメータを混ぜることができます。

class Foo {
  final String a;
  final String? b;
  Foo(this.a, {this.b});
}

void main() {
  Foo("ho", b: "ge");
}

必須のパラメータは名前無しで、設定値がたくさんあるけど、一部しか設定しないことが多いパラメータは名前付きにすることでスッキリ指定できる。これは公式のライブラリでも多用されていますね。

パラメータを省略する

一部のパラメータを省略できるようにしたいとき、メソッドと同じようにパラメータに[]をつけることで省略可能になります。

名前無し
class Foo {
  final String a;
  final String? b;
  Foo(
    this.a,
    [this.b]
  );
}

名前付きの場合は、requiredを付けなければよいです。

名前付き
class Foo {
  final String a;
  final String? b;
  Foo({
    required this.a,
    this.b,
  });
}

パラメータを省略したときのデフォルト値を設定する

デフォルト値が定数

デフォルト値に定数を設定する場合は=を使って設定できます。

名前無し
class Foo {
  final String a;
  final String b;
  Foo(
    this.a,
    [this.b = "hoge"]
  );
}
名前付き
class Foo {
  final String a;
  final String b;
  Foo({
    required this.a,
    this.b = "hoge",
  });
}

デフォルト値が定数ではない

では定数ではない値をデフォルト値にするとどうなるでしょうか。

class Foo {
  final String a;
  final String b;
  Foo({
    required this.a,
    this.b = DateTime.now().toIso8601String();
    // The default value of an optional parameter must be constant.
  });
}

「オプショナルな引数のデフォルト値には定数しか設定できない」というエラーが表示されます。
こういう場合にはInitializer listを使います。

class Foo {
  final String a;
  final String b;
  Foo({
    required this.a,
  }) : b = DateTime.now().toIso8601String();
}

複雑な初期化を書きたい

わかりますその気持ち。lateを使うとコンストラクタボディに初期化を書くことができます。

class Foo {
  final String a;
  late final String b;
  Foo(this.a) {
    b = DateTime.now().toIso8601String();
  }
}

ただlateを使うより、DateTime.parseのようなstaticなメソッドを用意することを検討したほうが良いでしょう。

参考文献

Discussion