🏗️

【Flutter】コンストラクタとsuperについて

2023/05/15に公開

誰もが目にするFlutterのお馴染みのこれ。

class MyHomePage extends StatefulWidget {
  MyHomePage({super.key, required this.title});


// ========== Dart2.17以前 ==========
class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

おまじないのように毎回使用していたけど、コンストラクタとsuperの関係性をいまいち理解せずに使っていたので、改めちゃんと調べてみた。


検証環境
// dart --version
Dart SDK version: 2.19.6 (stable) on "macos_arm64"

親子関係の無いクラス

まずは親子関係の無いクラスから順に見ていく。

「引数無し」「デフォルトコンストラクタ定義無し」

class Person {
    // 中身何も無し
}

void main() {
  // Personのインスタンス生成
  Person person = Person();
}
dart実行結果
// 何も処理は実行されない

 

「引数無し」「デフォルトコンストラクタ定義あり」

class Person {
    Person() {
        print('Personクラスのインスタンスが生成されました');
    }
}

void main() {
  Person person = Person();
}
dart実行結果
Personクラスのインスタンスが生成されました

 

「引数あり」「デフォルトコンストラクタ定義無し」

class Person {
  String? name;

  Person(this.name);
}

void main() {
  Person person = Person('山田');
  print(person.name);
}
dart実行結果
山田

 

「引数あり」「デフォルトコンストラクタ定義あり」

class Person {
  String? name;

  Person(this.name) {
    print('私は$nameです');
  }
}

void main() {
  Person person = Person('山田');
}
dart実行結果
私は山田です

 


任意のコンストラクタ処理を入れたい時は、名前付きコンストラクタを使用できる。

「引数無し」「名前付きコンストラクタ定義あり」

class Person {
  Person.feature1() {
    print('人は二足歩行です');
  }

  Person.feature2() {
    print('人は哺乳類です');
  }
}

void main() {
  // 名前付きにする事でPersonクラスのインスタンス生成時に、意図したコンストラクタを選択できる
  Person person1 = Person.feature1();
  Person person2 = Person.feature2();
}
dart実行結果
人は二足歩行です
人は哺乳類です

 

「引数あり」「名前付きコンストラクタ定義あり」

class Person {
  String? name;
  String? nationality;
  String? gender;

  Person({this.name, this.nationality, this.gender}) {
    print('Personクラスのインスタンスが生成されました');
  }

  Person.nationality(this.name, this.nationality) {
    print('$name$nationalityです');
  }

  Person.gender(this.name, this.gender) {
    print('$name$genderです');
  }
}

void main() {
  // 名前付きにする事でPersonクラスのインスタンス生成時に、意図したコンストラクタを選択できる
  Person person = Person();
  Person person1 = Person.nationality('山田', '日本人');
  Person person2 = Person.gender('山田', '男性');
}
dart実行結果
Personクラスのインスタンスが生成されました
山田は日本人です
山田は男性です

 

親クラスを継承するクラスの場合

親クラスを継承したクラスを作成する場合、子クラスのコンストラクタで親クラスのコンストラクタを呼び出す必要がある。

パターン①

// 親クラス
class Person {
  Person() {
    print('Personクラスのインスタンスが生成されました');
  }
}

// 子クラス
class Yamada extends Person {
  Yamada() : super() { 
    print('Yamadaクラスのインスタンスが生成されました');
  }
  
  // 親クラスが「引数無し」かつ「デフォルトコンストラクタ」なら、super()は省略可能。以下の記載でもOK
  // Yamada() {
  //   print('Yamadaクラスのインスタンスが生成されました');
  // }
}

void main() {
  Yamada yamada = Yamada();
}
dart実行結果
Personクラスのインスタンスが生成されました
Yamadaクラスのインスタンスが生成されました

 

パターン②

// 親クラス
class Person {
  String? name;

  Person(this.name) {
    print('私は$nameです');
  }
}

// 子クラス
class Yamada extends Person {
  Yamada() : super('山田') {
    print('Yamadaクラスのインスタンスが生成されました');
    // 継承した親クラスの変数が使える
    print('親クラスのnameの値は「$name」です');
  }
}

void main() {
  Yamada yamada = Yamada();
}
dart実行結果
私は山田です
Yamadaクラスのインスタンスが生成されました
親クラスのnameの値は「山田」です

 

パターン③

// 親クラス
class Person {
  String? name;

  Person(this.name) {
    print('私は$nameです');
  }
}

// 子クラス
class Yamada extends Person {
  // 子クラスの引数の値を親クラス継承時に渡せます  
  Yamada(name) : super(name) {
    print('Yamadaクラスのインスタンスが生成されました');
  }

  // 子クラスの引数の値を親クラス継承時に渡す場合は、下記のように記載省略可能。 
  // Yamada(super.name){
  //   print('Yamadaクラスのインスタンスが生成されました');
  // }

}

void main() {
  Yamada yamada = Yamada('山田');
}
dart実行結果
私は山田です
Yamadaクラスのインスタンスが生成されました

 

パターン④

superを使って親クラスのメソッドを呼び出すこともできる。

// 親クラス
class Person {
  Person() {
    print('Personクラスのインスタンスが生成されました');
  }

  void speakLanguages() {
    print('私は日本語を喋ります');
  }
}

// 子クラス
class Yamada extends Person {
  Yamada() : super() {
    print('Yamadaクラスのインスタンスが生成されました');
    super.speakLanguages();
  }
}

void main() {
  Yamada yamada = Yamada();
}
dart実行結果
Personクラスのインスタンスが生成されました
Yamadaクラスのインスタンスが生成されました
私は日本語を喋ります

 

Flutterで使用する場合

Flutterで使用する場合の一例。

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'sample',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('sample app'),
        ),
        body: MyCustomContainer(
          child: const MyCustomText('Hello, Flutter!'),
        ),
      ),
    );
  }
}

class MyCustomContainer extends Container {
  MyCustomContainer({
    super.key,
    super.child,
  }) : super(
          margin: const EdgeInsets.all(20.0),
          padding: const EdgeInsets.all(20.0),
          color: Colors.blue,
        );

  // superの書き方省略しない場合
  // MyCustomContainer({Key? key, Widget? child})
  //     : super(
  //           key: key,
  //           margin: const EdgeInsets.all(20.0),
  //           padding: const EdgeInsets.all(20.0),
  //           color: Colors.blue,
  //           child: child);
}

class MyCustomText extends Text {
  const MyCustomText(
    super.data, {
    super.key,
  }) : super(
          style: const TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 30.0,
            fontStyle: FontStyle.italic,
            color: Colors.purple,
          ),
        );

  // superの書き方省略しない場合
  // const MyCustomText(String data, {Key? key})
  //     : super(data,
  //           key: key,
  //           style: const TextStyle(
  //             fontWeight: FontWeight.bold,
  //             fontSize: 30.0,
  //             fontStyle: FontStyle.italic,
  //             color: Colors.purple,
  // ));
}

assert

assertはコンストラクタ内で使用でき、デバック時にのみ有効で、条件式がfalseの場合にエラーを発生させる。
リリースビルドではassertは無視されるため、エラーハンドリングやバリデーションには使用できないこと注意。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'sample',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('sample app'),
        ),
        body: MyCustomContainer(
          // 検証用にnullに変更
          child: null,
          // child: const MyCustomText('Hello, Flutter!'),
        ),
      ),
    );
  }
}

class MyCustomContainer extends Container {
  MyCustomContainer({
    super.key,
    super.child,
  })  
  // assetを追加
  : assert(child != null), // childがnullの場合は_AssertionErrorとなる
        super(
          margin: const EdgeInsets.all(20.0),
          padding: const EdgeInsets.all(20.0),
          color: Colors.blue,
        );
}

class MyCustomText extends Text {
  const MyCustomText(
    super.data, {
    super.key,
  }) : super(
          style: const TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 30.0,
            fontStyle: FontStyle.italic,
            color: Colors.purple,
          ),
        );
}

参考

https://dart.dev/language/constructors#invoking-a-non-default-superclass-constructor

https://codewithandrea.com/tips/dart-2.17-super-initializers/#automatic-migration-with-dart-fix

https://dart.dev/language/error-handling#assert

GitHubで編集を提案

Discussion