メインコンテンツにスキップ

コンストラクタ

コンストラクタは、クラスのインスタンスを作成する特別な関数です。

Dartは多くの種類のコンストラクタを実装しています。デフォルトコンストラクタを除き、これらの関数はクラスと同じ名前を持ちます。

生成コンストラクタ
新しいインスタンスを作成し、インスタンス変数を初期化します。
デフォルトコンストラクタ
コンストラクタが指定されていない場合に新しいインスタンスを作成するために使用されます。引数を受け取らず、名前もありません。
名前付きコンストラクタ
コンストラクタの目的を明確にするか、同じクラスに対して複数のコンストラクタを作成できるようにします。
定数コンストラクタ
コンパイル時定数としてインスタンスを作成します。
ファクトリコンストラクタ
サブタイプの新しいインスタンスを作成するか、キャッシュから既存のインスタンスを返します。
リダイレクトコンストラクタ
同じクラスの別のコンストラクタへの呼び出しを転送します。

コンストラクタの種類

#

生成コンストラクタ

#

クラスをインスタンス化するには、生成コンストラクタを使用します。

dart
class Point {
  // Instance variables to hold the coordinates of the point.
  double x;
  double y;

  // Generative constructor with initializing formal parameters:
  Point(this.x, this.y);
}

デフォルトコンストラクタ

#

コンストラクタを宣言しない場合、Dartはデフォルトコンストラクタを使用します。デフォルトコンストラクタは、引数や名前のない生成コンストラクタです。

名前付きコンストラクタ

#

名前付きコンストラクタを使用して、クラスに複数のコンストラクタを実装するか、追加の明確性を提供します。

dart
const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // Sets the x and y instance variables
  // before the constructor body runs.
  Point(this.x, this.y);

  // Named constructor
  Point.origin() : x = xOrigin, y = yOrigin;
}

サブクラスは、スーパークラスの名前付きコンストラクタを継承しません。スーパークラスで定義された名前付きコンストラクタを持つサブクラスを作成するには、サブクラスでそのコンストラクタを実装します。

定数コンストラクタ

#

クラスが不変のオブジェクトを生成する場合、これらのオブジェクトをコンパイル時定数にします。オブジェクトをコンパイル時定数にするには、すべてのインスタンス変数がfinalに設定されたconstコンストラクタを定義します。

dart
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

定数コンストラクタは常に定数を作成するわけではありません。非constコンテキストで呼び出される可能性があります。詳細については、コンストラクタの使用セクションを参照してください。

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

#

コンストラクタは、同じクラスの別のコンストラクタにリダイレクトする場合があります。リダイレクトコンストラクタには空の本体があります。コンストラクタは、コロン(:)の後にクラス名の代わりにthisを使用します。

dart
class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

ファクトリコンストラクタ

#

コンストラクタを実装する次の2つのケースのいずれかに遭遇した場合、factoryキーワードを使用します。

  • コンストラクタは必ずしもそのクラスの新しいインスタンスを作成するわけではありません。ファクトリコンストラクタはnullを返すことはできませんが、返される場合があります

    • 新しいインスタンスを作成する代わりにキャッシュから既存のインスタンス
    • サブタイプの新しいインスタンス
  • インスタンスを構築する前に、自明でない作業を実行する必要があります。これには、引数のチェックや、初期化リストで処理できないその他の処理が含まれる場合があります。

次の例には、2つのファクトリコンストラクタが含まれています。

  • Loggerファクトリコンストラクタは、キャッシュからオブジェクトを返します。
  • Logger.fromJsonファクトリコンストラクタは、JSONオブジェクトからfinal変数を初期化します。
dart
class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

ファクトリコンストラクタを他のコンストラクタと同様に使用する

dart
var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

リダイレクトファクトリコンストラクタ

#

リダイレクトファクトリコンストラクタは、リダイレクトコンストラクタへの呼び出しがあったときに使用する別のクラスのコンストラクタへの呼び出しを指定します。

dart
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable

通常のファクトリコンストラクタは他のクラスのインスタンスを作成して返すことができるように見えるかもしれません。これにより、リダイレクトファクトリは不要になります。リダイレクトファクトリにはいくつかの利点があります。

  • 抽象クラスは、別のクラスの定数コンストラクタを使用する定数コンストラクタを提供できます。
  • リダイレクトファクトリコンストラクタは、フォーマルパラメータとそのデフォルト値を繰り返すためのフォワーダーの必要性を回避します。

コンストラクタのティアオフ

#

Dartでは、コンストラクタを呼び出さずにパラメータとして提供できます。ティアオフ(括弧をティアオフする)と呼ばれるものは、同じパラメータでコンストラクタを呼び出すクロージャとして機能します。

ティアオフが、メソッドが受け入れるものと同じシグネチャと戻り値の型を持つコンストラクタである場合、ティアオフをパラメータまたは変数として使用できます。

ティアオフはラムダまたは匿名関数とは異なります。ラムダはコンストラクタのラッパーとして機能しますが、ティアオフはコンストラクタ自体です。

ティアオフを使用する

gooddart
// Use a tear-off for a named constructor:
var strings = charCodes.map(String.fromCharCode);

// Use a tear-off for an unnamed constructor:
var buffers = charCodes.map(StringBuffer.new);

ラムダではない

baddart
// Instead of a lambda for a named constructor:
var strings = charCodes.map((code) => String.fromCharCode(code));

// Instead of a lambda for an unnamed constructor:
var buffers = charCodes.map((code) => StringBuffer(code));

詳細については、ティアオフに関するこのDecoding Flutterビデオをご覧ください。

YouTubeで新しいタブで視聴:「Dart Tear-offs | Decoding Flutter」

インスタンス変数の初期化

#

Dartは3つの方法で変数を初期化できます。

宣言時にインスタンス変数を初期化する

#

変数を宣言するときにインスタンス変数を初期化します。

dart
class PointA {
  double x = 1.0;
  double y = 2.0;

  // The implicit default constructor sets these variables to (1.0,2.0)
  // PointA();

  @override
  String toString() {
    return 'PointA($x,$y)';
  }
}

初期化フォーマルパラメータを使用する

#

コンストラクタ引数をインスタンス変数に割り当てる一般的なパターンを簡略化するために、Dartには初期化フォーマルパラメータがあります。

コンストラクタ宣言でthis.<propertyName>を含め、本体を省略します。thisキーワードは現在のインスタンスを参照します。

名前の競合がある場合は、thisを使用します。それ以外の場合、Dartスタイルはthisを省略します。生成コンストラクタの例外があり、初期化フォーマルパラメータ名の前にthisを付ける必要があります。

このガイドの前のセクションで述べたように、特定のコンストラクタおよびコンストラクタの特定のセクションはthisにアクセスできません。これらには以下が含まれます。

  • ファクトリコンストラクタ
  • 初期化リストの右辺
  • スーパークラスコンストラクタの引数

初期化フォーマルパラメータは、非null許容またはfinalインスタンス変数を初期化することもできます。これらのいずれのタイプの変数も、初期化またはデフォルト値が必要です。

dart
class PointB {
  final double x;
  final double y;

  // Sets the x and y instance variables
  // before the constructor body runs.
  PointB(this.x, this.y);

  // Initializing formal parameters can also be optional.
  PointB.optional([this.x = 0.0, this.y = 0.0]);
}

プライベートフィールドは、名前付き初期化フォーマルとして使用できません。

dart
class PointB {
// ...

  PointB.namedPrivate({required double x, required double y})
      : _x = x,
        _y = y;

// ...
}

これは名前付き変数でも機能します。

dart
class PointC {
  double x; // must be set in constructor
  double y; // must be set in constructor

  // Generative constructor with initializing formal parameters
  // with default values
  PointC.named({this.x = 1.0, this.y = 1.0});

  @override
  String toString() {
    return 'PointC.named($x,$y)';
  }
}

// Constructor using named variables.
final pointC = PointC.named(x: 2.0, y: 2.0);

初期化フォーマルパラメータから導入されたすべての変数は、finalであり、初期化された変数のスコープ内のみです。

初期化リストで表現できないロジックを実行するには、そのロジックを持つファクトリコンストラクタまたは静的メソッドを作成します。その後、計算された値を通常のコンストラクタに渡すことができます。

コンストラクタパラメータはnull許容として設定され、初期化されない可能性があります。

dart
class PointD {
  double? x; // null if not set in constructor
  double? y; // null if not set in constructor

  // Generative constructor with initializing formal parameters
  PointD(this.x, this.y);

  @override
  String toString() {
    return 'PointD($x,$y)';
  }
}

初期化リストを使用する

#

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

dart
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

開発中に入力を検証するには、初期化リストでassertを使用します。

dart
Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

初期化リストはfinalフィールドの設定に役立ちます。

次の例では、初期化リストで3つのfinalフィールドを初期化します。コードを実行するには、実行をクリックします。

import 'dart:math';

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
    : x = x,
      y = y,
      distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
}

コンストラクタの継承

#

サブクラス、または子クラスは、スーパークラス、または直接の親クラスからコンストラクタを継承しません。クラスがコンストラクタを宣言しない場合、デフォルトコンストラクタのみを使用できます。

クラスは、スーパークラスのパラメータを継承できます。これらはスーパーパラメータと呼ばれます。

コンストラクタは、静的メソッドのチェーンを呼び出す方法といくぶん似ています。各サブクラスは、インスタンスを初期化するためにスーパークラスのコンストラクタを呼び出すことができます。これは、サブクラスがスーパークラスの静的メソッドを呼び出すことができるのと似ています。このプロセスでは、コンストラクタの本体やシグネチャは「継承」されません。

デフォルト以外のスーパークラスコンストラクタ

#

Dartは次の順序でコンストラクタを実行します。

  1. 初期化リスト
  2. スーパークラスの匿名、引数なしコンストラクタ
  3. メインクラスの引数なしコンストラクタ

スーパークラスに匿名、引数なしコンストラクタがない場合、スーパークラスのコンストラクタのいずれかを呼び出します。コンストラクタ本体(存在する場合)の前に、コロン(:)の後にスーパークラスコンストラクタを指定します。

次の例では、Employeeクラスのコンストラクタが、そのスーパークラスであるPersonの名前付きコンストラクタを呼び出します。次のコードを実行するには、実行をクリックします。

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson().
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // Prints:
  // in Person
  // in Employee
  // Instance of 'Employee'
}

Dartはスーパークラスコンストラクタの引数をコンストラクタを呼び出す前に評価するため、引数は関数呼び出しのような式にすることができます。

dart
class Employee extends Person {
  Employee() : super.fromJson(fetchDefaultData());
  // ···
}

スーパーパラメータ

#

コンストラクタのスーパー呼び出しに各パラメータを渡すのを避けるために、スーパーイニシャライザパラメータを使用して、指定されたまたはデフォルトのスーパークラスコンストラクタにパラメータを転送します。この機能はリダイレクトコンストラクタでは使用できません。スーパーイニシャライザパラメータは、初期化フォーマルパラメータと同様の構文とセマンティクスを持っています。

スーパーコンストラクタ呼び出しに位置引数が含まれる場合、スーパーイニシャライザパラメータは位置引数にできません。

dart
class Vector2d {
  final double x;
  final double y;

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

class Vector3d extends Vector2d {
  final double z;

  // Forward the x and y parameters to the default super constructor like:
  // Vector3d(final double x, final double y, this.z) : super(x, y);
  Vector3d(super.x, super.y, this.z);
}

さらに説明するために、次の例を検討してください。

dart
  // If you invoke the super constructor (`super(0)`) with any
  // positional arguments, using a super parameter (`super.x`)
  // results in an error.
  Vector3d.xAxisError(super.x): z = 0, super(0); // BAD

この名前付きコンストラクタは、x値を2回設定しようとします。1回はスーパーコンストラクタで、もう1回は位置スーパーパラメータとしてです。どちらもx位置引数に対処するため、エラーが発生します。

スーパーコンストラクタに名前付き引数がある場合、それらを名前付きスーパーパラメータ(次の例のsuper.y)とスーパーコンストラクタ呼び出しへの名前付き引数(super.named(x: 0))に分割できます。

dart
class Vector2d {
  // ...
  Vector2d.named({required this.x, required this.y});
}

class Vector3d extends Vector2d {
  final double z;

  // Forward the y parameter to the named super constructor like:
  // Vector3d.yzPlane({required double y, required this.z})
  //       : super.named(x: 0, y: y);
  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}