Dartツアー(5)クラス

技術,プログラミング言語

Flutterを始めてみましたが、コードはDart言語で書きます。という訳で、Dart言語のツアーを見て勉強しながら、自分用にメモをまとめてみました。今回はクラスです。

前回分は以下から。

クラス

  • DartはクラスとMix-inベースの継承をもったオブジェクト指向言語である
  • Nullを除くすべてのクラスはObjectを継承している
  • Mix-inベースの継承では、すべてのクラスが1つのスーパークラスを持つ(トップのObjectを除く)
    • ただし、クラス本体は複数のクラス階層で再利用できる
  • 拡張メソッドはクラスを変更したり、サブクラスを作成せずに、クラスに機能を追加できる

クラスメンバの使用

  • オブジェクトは関数とデータからなるメンバを持つ。
    • 関数をメソッド、データをインスタンス変数と呼ぶ
  • インスタンス変数やメソッドの参照にはドット(.)を使う
  • サタンのオペラんどがNULLの場合の例外を避けるには、"."の代わりに”?.”を使う

コンストラクタの使用

  • コンストラクタを使ったオブジェクトを作成できる
  • コンストラクタの名前は「クラス名」か「クラス名.名前」のどちらでも良い
    • 例えば、Pointオブジェクトを作成する際にPoint()とPoint.fromJson()コンストラクタを使うことができる
    • コンストラクタ名の前にnewをつけてもつけなくても同じ
  • 一部のクラスは定数コンストラクタを提供する。
    • 定数コンストラクタを使ってコンパイル時定数を作るには、コンストラクタ名の前にconstキーワードをつける
    • 同じコンパイル時定数であれば1つのオブジェクトになる
  • 定数コンテキスト内ではコンストラクタやリテラルの前につけるconstを省略できる

オブジェクトタイプの取得

実行時のオブジェクトタイプを取得するには、ObjectのruntimeTypeプロパティを使うことができる。

インスタンス変数

  • 初期化されていないすべてのインスタス変数の値はnullになる
  • すべてのインスタンス変数には暗黙のゲッターメソッドが生成される
  • 初期化子を持たない非finalインスタンス変数とlate finalインスタンス変数も暗黙のセッターメソッドを生成する
  • 宣言された場所で初期化した非lateインスタンス変数では、インスタンス作成時にコンストラクタが初期化リストが実行される前に値がセットされる。

インスタンス変数はfinalにできる。その場合、コンストラクタの初期化リストで一度だけ設定しなければならない。

もしfinalインスタンス変数をコンストラクタ本体を開始後に値を代入したい場合、以下のいずれかで可能。

  • ファクトリ・コンストラクタを使用する(ファクトリコンストラクタについては後で出てくる)
  • late finalを使う、しかし注意が必要(初期化子なしのlate finaはAPIにセッターを追加してしまう)

コンストラクタ

  • コンストラクタを宣言するには、そのクラスと同じ名前の関数を作成する
  • 任意で名前付きコンストラクタで説明する識別子を追加することもできる
  • もっとも一般的なコンストラクタである生成 コンストラクタはクラスの新しいインスタンスを作成する
  • thisキーワードは現在のインスタンスを参照する
    • thisは名前の衝突を防ぐ場合のみ使う。そのほかの場合はthisを省略する
    • コンストラクタの引数をインスタンス変数に代入することは一般的なので、Dartでは簡単な構文を用意している

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

コンストラクタを宣言しない場合、デフォルトコンストラクタが提供される。引数を持たず、スーパークラスの無引数コンストラクタを呼び出す。

コンストラクタは継承しない

サブクラスはスーパークラスからコンストラクタを継承しない。コンストラクタを宣言していないサブクラスは、デフォルトコンストラク(引数なし、名前なし)しか持たない。

名前付きコンストラクタ

あるクラスに複数のコンストラクタを実装したり、特に明快さを求める場合には、名前付きコンストラクタを使う。

コンストラクタは継承されないので、スーパークラスの名前付きコンストラクタもサブクラスには継承されない。サブクラスで使用したい場合には、サブクラスでその名前付きコンストラクタを実装する必要がある。

デフォルトでないスーパークラスコンストラクタの呼び出し

デフォルトではサブクラスのコンストラクタはスーパークラスの無名コンストラクタ(引数なし)を呼び出す。スーパークラスのコンストラクタはコンストラクタ本体の先頭で呼び出される。初期化リストが使用されている場合、スーパークラスが呼び出される前に実行される。実行順序をまとめると次の通りになる。

  1. 初期化リスト
  2. スーパークラスの引数なしコンストラクタ
  3. そのクラスの引数なしコンストラクタ

スーパークラスに無名、無引数コンストラクタがない場合、手動でいずれかのコンストラクタを呼び出す必要がある。スーパークラスのコンストラクタはコロンの後、(あれば)コンストラクタ本体の直前で指定する。

次の例では、Employeeクラスのコンストラクタが、そのスーパークラスPersonの名前付きコンストラクタを呼び出す。

スーパークラスのコンストラクタの引数はコンストラクタを呼び出す前に評価するため、関数呼び出しのように式にできる。ただし、スーパークラスのコンストラクタはthisへのアクセス権はない。例えば、静的メソッドを呼び出すことはできるが、インスタンスメソッドを呼び出すことはできない。

初期化リスト

スーパークラスのコンストラクタを呼び出す以外、コンストラクタが実行される前にインスタンス変数を初期化できる。初期化子は間まで区切る。ただし、初期化子の右側はthisにアクセスできない。

初期化リストはfinalフィールドを設定するのに便利である。次の例は初期化リストの3つのfinalフィールドを初期化するものである。

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

コンストラクタは同じクラスのコンストラクタにリダイレクトすることだけが目的であることもある。リダイレクトするコンストラクタの本体は空でコロンの後にコンストラクタの呼び出しを示す。

定数コンストラクタ

もしクラスが不変のオブジェクトを生成する場合、これらのオブジェクトをコンパイル時に定数化できる。そのためにはコンストラクタを定義し、すべてのインスタンス変数をfinalにする。定数コンストラクタは必ずしも定数を生成するわけではない。詳しくはコンストラクタの使い方のセクションを参照。

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

コンストラクタが常にそのクラスの新しいインスタンスを作成するとは限らない場合、factoryキーワードを使用する。例えば、ファクトリーコンストラクタはキャッシュからインスタンスを返したり、サブタイプのインスタンスを返したりすることがある。ファクトリコンストラクタのもう1つの使用例は初期化リストで処理できないロジックを使用してfinal変数を初期化することである。ただし、ファクトリコンストラクタはthisにアクセスできない。ファクトリコンストラクタは他のコンストラクタと同様に呼び出すことができる。

以下の例ではLoggerファクトリコンストラクタがキャッシュからオブジェクトを返す。そして、Logger.fromJsonファクトリコンストラクタがJSONオブジェクトからfinal変数を初期化する。

メソッド

メソッドはオブジェクトに振る舞いを提供する関数です。

インスタンスメソッド

オブジェクトのインスタンスメソッドはインスタンス変数とthisにアクセスすることができる。

演算子

演算子は特別な名前を持つインスタンスメソッドです。Dartは以下の名前の演算子を定義できる。

<+|>>>
>/^[]
<=~/&[]=
>=*<<~
%>>==
演算子

!=の様に上表にない演算子もあるが、それは単なる糖衣構文のためである。例えば、e1!=e2という式は!(e1 ==e2)の糖衣構文である。

演算子の宣言は組み込み識別子operatorを使う。

ゲッターとセッター

ゲッターとセッターはオブジェクトのプロパティへの読み取りと書き込みのアクセスを提供する特別なメソッドです。書くインスタンス変数には暗黙のゲッターと必要に応じてセッターがある。ゲッターとセッターを実装することで、get/setキーワードを使用して追加のプロパティを作成できる。

ゲッターとセッターによって、インスタンス変数から始めて、後でメソッドでラップすることができる。その際に呼び出し側のコードを変更しなくて良い。インクリメント演算子(++)のような演算子は明示的に定義しなくても期待通りに動く。予期せぬ副作用を避けるため、演算子はゲッターを一度だけ呼び出し、その値を一次変数に保持する。

抽象メソッド

  • インスタンスメソッド、ゲッター、セッターはインターフェイスは定義するが、その実装は他のクラスに委ねる抽象クラスにできる
  • 抽象メソッドは抽象クラス内にのみ存在することができる
  • 抽象メソッドはメソッド本体の代わりにセミコロンを使う。

抽象クラス

  • abstract修飾子を使うと抽象クラス(インスタンス化できないクラス)を定義できる
  • 抽象クラスは多くは何らかの実装を伴うインターフェイスを定義するのに便利である
  • 抽象クラスをインスタンス化できるように見せたい場合はファクトリコンストラクタを定義する
  • 抽象クラスは抽象メソッドを持つことが多い

暗黙のインターフェイス

  • 全てのクラスはそのクラスとクラスが実装するインターフェイスの全てのインスタンスメンバを含むインターフェイスを暗黙で定義している
  • クラスBの実装を継承せずにクラスBのAPIをサポートするクラスAを作る場合、クラスAはBのインターフェイスを実装する
  • クラスは1つまたは複数のインターフェイスをimplemets節で宣言し、必要とするAPIを提供することによってインターフェイスを実装する

クラスの拡張

  • extendsを使うとサブクラスを作成する
  • superでスーパークラスを参照する

メンバのオーバーライド

サブクラスでは(overetorを含む)インスタンスメソッド、ゲッター、セッターをオーバーライドできる。@overrideアノテーションで意図的にオーバーライドしていることを示すことができる。

オーバーライドするメソッドの宣言はオーバーライドするメソッドといくつかの点で一致しなければならない。

  • 戻り値の型はオーバーライドされたメソッドの戻り値の方と同じ型(ないしそのサブタイプ)である必要がある
  • 引数の型はオーバーライドされたメソッドの引数の型と同じ型(ないしその上位の型)である必要がある
  • オーバーライドされたメソッドがn個の位置パラメータを受け入れる場合、オーバーライドするメソッドもn個の位置パラメータを受け入れる必要がある
  • ジェネリックメソッドは非ジェネリックメソッドをオーバーライドできず、逆も同様である

メソッドのパラメータやインスタンス変数の型を絞り込みたいことがあるが、通常のルールに反しており、実行時に型エラーが発生する可能性がある点でダウンキャストと同様である。それでも、型エラーが発生しないことを保証できるコードであれば、convariantキーワードを使用することで可能である。詳細はDart言語仕様書を参照。

注:==をオーバーライドする場合、オブジェクトのhashCodeゲッターもオーバーライドすべきです。

noSuchMethod()

存在しないメソッドやインスタンス変数を使うコードを検出したり反応させたりするには、noSuchMethod()をオーバーライドします。

以下のいずれかを満たさない限り、未実装のメソッドを呼び出すことはできません。

  • レシーバが静的型のdynamicを持っている
  • レシーバが未実装のメソッドを定義した静的型(abstractでも良い)があり、レシーバの動的型にはクラスObjectのものとは異なるnoSuchMethod()の実装がある

拡張メソッド

拡張メソッドは既存のライブラリに機能を追加するため方法です。拡張メソッドは知らずに使っているかもしれません。例えば、IDEでコード補完を使うと、通常のメソッドと一緒に拡張メソッドも提案します。

列挙型

列挙型はしばしば列挙または列挙子と呼ばれ、固定数の低数値を表現するために使う特別な種類のクラスです。

列挙型の使用

  • enumキーワードで列挙型を定義する
  • 末尾にカンマがあっても良い
  • 列挙型の各値にはindexゲッターがあり、宣言の中でその値の0から始まる位置を返す
  • 列挙の全ての値のリストを得るにはvalues定数を使う
  • switch文で列挙を使用できる。全ての列挙値を処理しないと警告が出る
  • 列挙型は次の制限がある
    • サブクラス化、mixin、implement
    • 明示的なインスタンス化

クラスへの機能追加:mixin

  • MixInはクラスのコードを複数のクラス階層で再利用するための方法である
  • MixInを使用するにはwithキーワードに続けて1つ以上のMixIn名を使う
  • MixInを実装するにはObjectを継承し、コンストラクタを宣言していないクラスを作成する
  • MixInを通常のクラスとして使用しないのであればclassの代わりにmixinキーワードを使用する
  • あるMixInを使用できる型を制限したい場合、onキーワードを使用して必要なスーパークラスを指定することで制限できる。

クラス変数とメソッド

staticキーワードはクラス全体の変数とメソッドを実装するために使用する。

静的変数

  • 静的変数(クラス変数)はクラス横断の状態と定数に便利である
  • 静的変数は使用前までに初期化される

静的メソッド

  • 静的メソッド(クラスメソッド)はインスタンスを操作しないので、インスタンスにアクセスすることはできない
  • 静的変数へのアクセスは可能

注:一般的なユーティリティや広く使われている機能については、静的メソッドではなく、トップレベルの関数を使用することを検討してください。

静的メソッドは、コンパイル時の定数として使用することができます。たとえば、定数コンストラクタのパラメータとして静的メソッドを渡すことができます。

次回

次回はジェネリックです。

2022-04-04技術Dart,プログラミング言語

Posted by woinary