言語編 |
Dartはクラスとミクスイン・ベースの継承(mixin-based inheritance)を持ったオブジェクト指向の言語である。各オブジェクトはあるクラスのインスタンスであり、総てのクラスはObjectからの副型である。ミクスイン・ベースの継承ということは、各クラス(Objectを除いて)はまさしく一つのスーパークラスを有するものの、クラス・ボディは多重のクラス継承にたいし再使用できることを意味する。
オブジェクトたちは関数たちとデータ(メソッドとインスタンス変数)で構成される。あるメソッドを呼ぶときは、それをあるオブジェクト上で呼び出すことになる:そのメソッドはそのオブジェクトの関数とデータへのアクセスが可能である。
インスタンス変数またはメソッドを参照するときはドット (.)を使う:
|
一番左側の被演算数がnullのときの例外の生起を避けるには.の代わりに?.を使う:
|
コンストラクタを使ってオブジェクトを生成できる。コンストラクタの名前はクラス名(ClassName)またはクラス名.識別子(ClassName.identifier)である。例えば、以下のコードはPoint()及びPoint.fromJson()コンストラクタを使ってPointオブジェクトを生成している:
|
以下のコードは同じ効果を持っているが、コンストラクタの前にオプショナルなnewキーワードを使用している:
|
バージョンの注意:このnewキーワードはDart 2でオプショナルとなった。
一部のクラスたちは常数コンストラクタを用意している。常数コンストラクタを使ってコンパイル時常数を生成するには、そのコンストラクタ名の前にconsキーワードを付す:
|
2つの同じコンパイル時常数を作るということは単一の基準となるインスタンスをもたらすことである:
|
常数のコンテキストのなかでは、コンストラクタまたはリテラルの前のconstを省くことができる。例えばconst mapを生成するこのコードでは:
|
最初に使われているconstキーワードを除いたすべてのconstは省略できる:
|
ある常数コンストラクタがある常数コンテキスト外に合ってconstなしで呼ばれているときは、これは非コンスタントのオブジェクトを生成する:
|
バージョンの注意:Dart 2で、このconstキーワードは常数コンテキスト内でオプショナルとなった。
ランタイム中にあるオブジェクトの型を取得するには、Typeオブジェクトを返すObjectのruntimeTypeプロパティを使用する。
|
ここまでは如何にクラスたちを使用するかを見てきた。本章の残りではクラスたちをどのように実装するかを示す。
以下はどのようにインスタンス変数を宣言するかを示す:
|
初期化されていないインスタンス変数は値nullを持つ。
総てのインスタンス変数は暗示的(implicit)にゲッタ・メソッドを持つ。非finalはインスタンス変数もまた暗示的なセッタ・メソッドを持つ。詳細は「ゲッタとセッタ」の項を参照のこと。
|
インスタンス変数をそこで宣言された場所(コンストラクタやメソッドではなく)で初期化するときは、そのインスタンスが作られた時点で、即ちコンストラクタとそのイニシャライザ・リストが実行される前に、その値がセットされる。
そのクラス(加えてオプショナルなものとして指名コンストラクタ(Named constructors)のなかで記されているように追加的な識別子)と同じ名前を持った関数を作ることでコンストラクタを宣言する。コンストラクタの最も一般的な書式である生成的コンストラクタ(generative constructor)は、あるクラスの新しいインスタンスを生成する:
|
thisというキーワードは現行のインスタンスのことを言っている。
注意:名前がかち合っている場合にのみthisを使うこと。Dartのスタイルではthisは省かれている。
コンストラクタの引数にインスタンス変数を指定するのは極めて一般的で、Dartではそれを簡単化する糖衣構文(syntactic sugar)を持っている:
|
コンストラクタを宣言しないときはデフォルトのコンストラクタが用意されている。デフォルト・コンストラクタは引数を持たず、そのスーパークラスの中にある引数なしのコンストラクタを呼び出す。
サブクラスたちは自分たちのスーパークラスからコンストラクタを継承しない。コンストラクタを宣言していないサブクラスはデフォルトのコンストラクタ(引数も名前もない)のみを有する。
あるクラスに対し複数のコンストラクタを実装するとか、より明確性を持たせる為に指名コンストラクタを使う:
|
コンストラクタたちは継承されないことを思い出して欲しい。即ちスーパークラスの指名コンストラクタはサブクラスによって継承されない。もしそのスーパークラスで定義された指名コンストラクタを持ったサブクラスを生成したいときは、そのサブクラス内でそのコンストラクタを実装しなければならない。
デフォルトではサブクラス内のコンストラクタはそのスーパークラスの名前のない引数なしのコンストラクタを呼び出す。このスーパークラスのコンストラクタはそのコンストラクタのボディの開始前に呼ばれる。もし初期化子リスト(initializer list)が使われている場合は、それはスーパークラスが呼ばれる前にそれが実行される。纏めると実行順は以下のようになる:
初期化リスト
スーパークラスの引数なしのコンストラクタ
メインのクラスの引数なしのコンストラクタ
もしそのスーパクラスは名前なしで引数なしのコンストラクタを持っていないときは、そのスーパークラス内のコンストラクタたちひとつをマニュアルで呼ばねばならない。コロン(:)のあとでコンストラクタのボディ(もしあれば)のちょうど前でそのスーパークラスのコンストラクタを指定する。
以下の例では、EmployeeクラスのコンストラクタはそのスーパークラスであるPersonの名前付きコンストラクタを呼んでいる。DartPadでこれを実行してみると良い。
|
スーパークラスのコンストラクタへの引数はそのコンストラクタを呼び出す前に計算されるので、引数は関数呼び出しのようなある式になる得る:
|
警告:スーパークラスのコンストラクタへの引数はthisへのアクセスを持たない。例えば、引数たちはstaticメソッドたちを呼べるがインスタンス・メソッドたちは呼べない。
コンストラクタではイニシャライザ・リスト(initializer list)とボディを使って多様な初期化が可能である。イニシャライザ・リストはコロン(':')で始まり、カンマ(',')で分離されたイニシャライザたちのリストで構成される。スーパークラスのコンストラクタを呼び出すことの他にも、イニシャライズ・リストはボディ部の前に定義すべきフィールドたちの値をセットするのに使われる。これはfinalなフィールドには必要である。そのコンストラクタのボディが走る前にインスタンス変数たちを初期化することができる。
|
警告:初期化子の右側はthisへのアクセスを持たない。
開発中に初期化リストの中でassertを使って入力を検証できる。
|
初期化子リストはfinalなフィールドを設定するのに手頃なものである。以下のコードは初期化子リストの中の3つのfinalフィールドを初期化している:
|
これをDartPad等で実行すると3.605551275463989が表示される:
もう一つの例を示そう。ここでは初期化リストを使った原点設定のためのPoint.zeroというコンストラクタと極座標で初期化するPoint.polarというコンストラクタが使われている:
|
これをDartPad等で実行すると0.7071067811865476, 0.7071067811865475が表示される。
あるコンストラクタの唯一の目的が同じクラスの中の別のコンストラクタにリダイレクト(転送)するためという場合がある。リダイレクト・コンストラクタのボディは空で、コロン(:)のあとに出てくるコンストラクタ呼び出しがある。
|
この例ではX軸上のPointなので、Y軸は0である。従ってPoint.alongXAxis(num x)というコンストラクタは引数yを0にしてメインのコンストラクタを呼び出している。
そのクラスが決して変化しないオブジェクトを作るときは、それらのオブジェクトをコンパイル時常数にできる。そうするには、constコンストラクタを定義し、総てのインスタンス変数をfinalとする。
|
常数コンストラクタは必ずしも常に常数を作るわけではない。詳細は「コンストラクタを使う」の節を参照のこと。
Dartではファクトリ・コンストラクタ(Factory Constructor)と呼ばれる新しい種類のコンストラクタが導入されている(Javaではファクトリは一般的に使われるデザイン・パタンとなっている)。
ファクトリ・コンストラクタは、必ずしも常にそのクラスの新規インスタンスを生成するわけではないコンストラクタを実装する際にfactoryキーワードを付す。ファクトリ・コンストラクタは例えばあるキャッシュからあるインスタンスを返すとかある副型のインスタンスを返すとかに使われる。
ファクトリは一見staticなメソッドのように見えるが、明示的に指定したクラスのインスタンスを返すという点で異なっている。ファクトリはコンストラクタのように呼び出すことができ、そのメソッドのボディは返されるインスタンスを制御できる。ファクトリは他の言語でのコンストラクタに関わる弱点に対処している。ファクトリは新規に割り当てられたものではないインスタンスを生成できる:即ちこれらのインスタンスはキャッシュから得られる。同様にまた、ファクトリは異なったクラスのインスタンスを返すことが出来る。また、そのクラスのインスタンスを初期化するコードから分離して追加の処理コードが必要になるときにもこのコンストラクタは使用できる。
以下の例はあるキャッシュからオブジェクトたちを返すファクトリ・コンストラクタのデモである:
|
nameというString型の変数はfinal宣言されているので、Symbolクラスのインスタンスに固有の名前が与えられることになる。
インスタンスたちのキャッシュはMapで構成され、各インスタンスは名前付きで出し入れされる。このキャッシュの名前は_cacheとアンダスコアが先行しているのでprivateとなっている。
ファクトリはfactory Logger(String name)というシグネチュアになっている。デフォルト(名前付きでない)のコンストラクタにfactoryというプレフィックスが付いていることで、Dartはそれはファクトリ・コンストラクタであることを知る。このボディ部では以下のことが行われる:
キャッシュがまだ存在しない、即ちnullの状態なら、要素が空のマップとする。
キャッシュが存在しているなら、そのキャッシュに指定された名前のインスタンスが存在するかを調べる
もし存在するなら、そのインスタンスをとりだして返す。
存在しないならこのクラスのインスタンスを内部コンストラクタにより生成し、それを名前付きでキャッシュにストアするとともに、そのインスタンスを返す。
これにより、キャッシュには指定された名前のインスタンスが必ずひとつストアされる。
例えば呼び出し側で次のような2つのインスタンスを同じ名前で生成したとする:
|
最初のaというインスタンスは新しいインスタンスを生成したものであるが、bというインスタンスはキャッシュされていたものである。プログラム開発の最初の段階では通常のコンストラクタを使っていて、必要に応じファクトリを用意すれば良い。呼び出し側のコードはその為に変更しなくても良い。
注意:ファクトリ・コンストラクタはthisへのアクセスを持たない。
他のコンストラクタと同じようにファクトリ・コンストラクタが呼び出せる:
|
次の例はGoogleの技術者のSeth Laddが示しているDartにおけるシングルトン・パタンである:
|
2行目の_singletonはstaticでかつfinalなのでクラス変数であり、これはこのクラスの最初のインスタンス化で設定される。factory Singleton()というファクトリ・コンストラクタは従ってこのクラスでただひとつのオブジェクトを返す。main()のなかでnew Singleton();を呼ぶとこのファクトリ・コンストラクタが実行される。従って、s1やs2といったオブジェクトは全く同じオブジェクトとなる。
メソッドはあるオブジェクトの振る舞いを提供する関数である。
オブジェクト上のインスタンス・メソッドはインスタンス変数とthisへアクセスできる。以下のサンプルのdistanceTo()メソッドはインスタンス・メソッドの一例である:
|
ゲッタとセッタはあるオブジェクトのプロパティへの読み書きを提供する特別なメソッドである。各インスタンス変数は暗示的なゲッタ、加えて適切であればセッタを持っていることを思い出して頂きたい。getとsetキーワードを使ってゲッタとセッタを実装して新たなプロパティを作ることもできる:
|
戻り値が指定されていないときは、そのゲッタの型はdynamicである。またセッタ/ゲッタはメソッドをオーバライド出来ず、メソッドはまたセッタ/ゲッタをオーバライドできない。ゲッタとセッタを用いて、クライアントのコードを変更することなく、インスタンス変数で始め、後でそれらのインスタンス変数をメソッドで包むことができる。
注意:増分(++)のような演算子は、ゲッタが明示的に定義されているか否かに拘わらず期待されるやり方で動作する。予期せぬ副作用を回避するために、その演算子は一時的にその値を保管しつつただ1回そのゲッタを呼び出す。
ゲッタとセッタを使ったもうひとつの例を示す:
|
main()のなかでは、最初にmyVecというD2Vectorのオブジェクトを絶対値1.0、角度0で生成している。次のprint行ではmyVec.xValとmyVec.yValで、フィールドではない値をあたかもフィールドのように読みだしている。セットもmyVec.yVal = 1;のように、yValをセットしているが、実際はこれによりabsとargを変更している。ここではxValもyValも1になったので、absは√2でargはπ/2である。
このコードは説明のためのもので、実際には絶対値が0とかX長が0のときの処置、例えば例外をスローすることなどが必要である。
インスタンス、ゲッタ、及びセッタのメソッドはインターフェイスを定義して抽象とし、その実装は他のクラスに任せることができる。抽象メソッドは抽象クラス内にのみ存在できる。
あるメソッドを抽象とするには、メソッドのボディの代わりにセミコロン(;)を使う:
|
抽象クラス(インスタンス化出来ないクラス)を定義するときはabstract修飾子を使う。抽象クラスは何らかの実装とともにインターフェイスを定義するのに有用である。自分の抽象クラスがインスタンス化可能のように見えるようにしたいときは、ファクトリ・コンストラクタを定義する。
抽象クラスはしばしば抽象メソッドを持つ。以下は抽象メソッドを持った抽象クラスの宣言例である:
|
各クラスはそのクラスとそのクラスが実装しているインターフェイスのインスタンス・メンバたち総てを含んだインターフェイスが暗示的に定義されている。Bの実装を継承することなくクラスBのAPIをサポートするクラスAを作りたいときは、クラスAはBインターフェイスを実装しなければならない。
クラスはinplements句で宣言し、次にそのインターフェイスたちが必要とするAPIたちを提供することで、ひとつ或いはそれ以上のインターフェイスを実装する。例えば:
|
このコードを理解すればDartの暗示的インターフェイスの使い方をマスターしたことになるので、DartPadで確認されることをお勧めする。
main()のなかの最初の行において、
Person('Kathy')は外部からアクセス可能なコンストラクタで、_nameが'Kathy'であるPersonオブジェクトを作る。
greetBob(Person('Kathy')
)はこのオブジェクトのgreet()を引数'Bob'
で呼ぶので、
'Hello,
Bob. I am Kathy'
という文字列が返される
一方2番目の行では、
Imposer()は変え玉(Imposer)が生成される。このオブジェクトは_nameは’’すなわち空で、greet()関数は'Hi
$who. Do you know who I
am?'
という文字列を返す。このオブジェクトを引数として
greetBob
メソッドを呼ぶと変え玉
(Imposer)
の
greet
メソッドを引数
'Bob'
で呼ぶことになる。従って
'Hi
Bob. Do you know who I am?'
という文字列が返される
以下はあるクラスが複数のインターフェイスを実装することを指定する例である:
|
サブクラス(副型)を生成するにはextendsを使い、そのスーパークラスを参照するにはsuperを使う:
|
サブクラスたちはインスタンス・メソッド、ゲッタ、及びセッタをオーバライドできる。意図的にあるメンバをオーバライドしていることを示すためには@overrideアノテーションを付す:
|
型安全であるコードの中でメソッド・パラメタまたはインスタンス変数の型を狭めるにはcovariant(共変)キーワードが使える。
下表に示された演算子たちはオーバライド可能である。例えば、あるVectorクラスを定義したときは、2つのベクトルを加算するのに+メソッドを定義することになろう。
< |
+ |
| |
[] |
> |
/ |
^ |
[]= |
<= |
~/ |
& |
~ |
>= |
* |
<< |
== |
– |
% |
>> |
|
注意:読者は!=がオーバライド可能演算子でないことに気が付いたかもしれない。式 e1 != e2は単に式!(e1 == e2)の糖衣構文なのである。
以下は+と-の演算子をオーバライドしている例である:
|
もし==をオーバライドするときは、ObjectのhashCodeゲッタもオーバライドしなければならない。==とhashCodeをオーバライドしているサンプルは「マップ・キーの実装(Implementing map keys)」の項を見ること。
一般的なオーバライドに関する更なる情報は「クラスの拡張 (Extending a class)」を見て欲しい。
あるコードが存在しないメソッドやインスタンス変数を使用おうとしたときに検出あるいは反応するために、noSuchMethod()をオーバライドすることができる:
|
以下のひとつがtrueでない限り実装されていないメソッドを呼び出すことはできない:
そのレシーバがstaticかたのdynamicを持っている
未実装メソッド(抽象はOK)を定義しているstatic型を持っており、そのレシーバのdynamic型がObject クラス内のものから異なっているnoSuchMethod()の実装を持っている
更なる情報は、非公式の noSuchMethod forwarding specificationを見られたい。
列挙型はECMA-408の第2版(Dart 1.8)Genericsの前に追加された。列挙型(enumerated type)またはenumは固定数の定数値たちを表現するのに使われる。
列挙型はenumキーワードを使って宣言する:
|
列挙型の中の各値はindexゲッタを持っており、これがそのenum宣言の中の値の位置を0を起点として返す。例えば、最初の値はインデックス0を2番目の値はインデックス1を持っている。
|
そのenumの中の値の総てのリストを取得するには、そのenumのvalues常数を使用する。
|
列挙型はswitch文の中で使用でき、そのenumの値たちの総てを取り扱わないと警告が出てくる:
|
列挙型には以下の制限がある:
enumをサブクラス、ミクスイン、または実装できない
enumを明示的にインスタンス化できない
更なる情報はDart Language Specificationを参照のこと。
ミクスインは複数のクラス階層の中であるクラスコードを再利用する手段である。
ミクスインを使うにはwithキーワードを使い、そのあとにひとつまたはそれ以上のミクスインの名前を書く。以下の例はミクスインを使った2つのクラスを示している:
|
ミクスインの実装は、Objectを拡張したクラスを作り、コンストラクタたちは書かない。自分のミクスインが通常のクラスとして使えるようにしたい場合以外はclassの代わりにmixinキーワードを使用する。例えば:
|
一部の型たちのみがそのミクスインを使えるように指定したいとき—例えば、自分が定義していないメソッドが呼び出せるようにする—には、必要なスーパークラスを指定するのにonを使用する:
|
バージョンに関する注意:mixinキーワードのサ ポートはDart 2.1で導入されている。それ以前の版では通常その代わりに抽象クラスを使用していた。更なるこの変更に関する情報はDart SDK changelogと2.1 mixin specificationを見られたい。
クラスにわたる変数とメソッドをつくるにはstaticキーワードを使用する。
static変数(クラス変数)はクラスにわたる状態と常数を作るのに有用である:
|
Static変数たちはそれが使われるまでは初期化されていない。
もう一つ例を示す:
|
注意:このページは常数の名前にlowerCamelCase
を使うようにするという
スタイル・ガイド勧告
に従っている。
Staticメソッド(クラス・メソッド)はインスタンス上では動作せず、従ってthisへのアクセスを有さない。例えば:
|
注意:一般的にあるいは広く使われているユーティリティと機能の為には、staticメソッドではなくてトップ・レベルの関数を使用することを検討されたい。
staticメソッドはコンパイル時常数として使える。例えば、ある常数コンストラクタにパラメタとしてstatic・メソッドを渡すことができる。
クラスではなくあるオブジェクトに対し動的に属性を付加したいこともあろう。DartではExpando<T>というクラスが用意されている。
例えば次の例を見てみよう:
|
これはPersonというクラスに対してではなく、そのオブジェクトに対しnationalityとかageとかいう名前の属性を付加した例である。
対象となるオブジェクトは以下のものであってはいけない:
bool
num
String
null
付加するオブジェクトはこの例にあるように、Stringなどだけでなく、Mapなども使用できる。
JavaScriptでは総てのオブジェクトexpandだともいえるが、DartではExpandoオブジェクトを生成しなければならない。例えばJavaScriptでは:
|
において、myPropにある値を代入したとたんに属性であるmyPropが、例えそれが以前存在していなかったとしても動的に生成される。他の殆どの言語にはそのようなものは存在しない。
ティアオフとはその名の通りあるオブジェクトのメソッドを引き剥がしてクロージャ化することである。
次の例を見てみよう:
|
main()関数の中の2行目ではsayHiメソッドを引き剥がしている。そのメソッドを直接呼び出しているのではなく、それが呼ばれたときに該メソッドを呼び出すクロージャ(第1級関数)を返している。
Dartでは括弧で囲まれた引数リストが存在しないときに、それはそのメソッドを呼び出すのではなくてティアオフだと判断している。これはC#、Python、JSなどと同じである。しかしながらこれは引数を持たないゲッタ等では機能しない。