前のページ

言語編

次のページ


クラス (Exceptions)

Dartはクラスとミクスイン・ベースの継承(mixin-based inheritance)を持ったオブジェクト指向の言語である。各オブジェクトはあるクラスのインスタンスであり、総てのクラスはObjectからの副型である。ミクスイン・ベースの継承ということは、各クラス(Objectを除いて)はまさしく一つのスーパークラスを有するものの、クラス・ボディは多重のクラス継承にたいし再使用できることを意味する。



クラス・メンバの使用 (Using class members)

オブジェクトたちは関数たちとデータ(メソッドとインスタンス変数)で構成される。あるメソッドを呼ぶときは、それをあるオブジェクト上で呼び出すことになる:そのメソッドはそのオブジェクトの関数とデータへのアクセスが可能である。

インスタンス変数またはメソッドを参照するときはドット (.)を使う:

var p = Point(2, 2);
// インスタンス変数yに値をセット
p.y = 3;
// yの値を取得
assert(p.y == 3);
// p上のdistanceTo()を呼び出す
num distance = p.distanceTo(Point(4, 4));

一番左側の被演算数がnullのときの例外の生起を避けるには.の代わりに?.を使う:

// pが非nullの時はそのy4をセットする
p?.y = 4;



コンストラクタを使う (Using constructors)

コンストラクタを使ってオブジェクトを生成できる。コンストラクタの名前はクラス名(ClassName)またはクラス名.識別子(ClassName.identifier)である。例えば、以下のコードはPoint()及びPoint.fromJson()コンストラクタを使ってPointオブジェクトを生成している:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

以下のコードは同じ効果を持っているが、コンストラクタの前にオプショナルなnewキーワードを使用している:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

バージョンの注意:このnewキーワードはDart 2でオプショナルとなった。

一部のクラスたちは常数コンストラクタを用意している。常数コンストラクタを使ってコンパイル時常数を生成するには、そのコンストラクタ名の前にconsキーワードを付す:

var p = const ImmutablePoint(2, 2);

2つの同じコンパイル時常数を作るということは単一の基準となるインスタンスをもたらすことである:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // abも同じインスタンスである!

常数のコンテキストのなかでは、コンストラクタまたはリテラルの前のconstを省くことができる。例えばconst mapを生成するこのコードでは:

// ここでは多くのconstキーワードが使われている
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

最初に使われているconstキーワードを除いたすべてのconstは省略できる:

// ただ一つのconst、これが常数コンテキストを確立する
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

ある常数コンストラクタがある常数コンテキスト外に合ってconstなしで呼ばれているときは、これは非コンスタントのオブジェクトを生成する:

var a = const ImmutablePoint(1, 1); // 常数を生成
var b = ImmutablePoint(1, 1);       // 常数を生成しない
assert(!identical(a, b));           // 同じインスタンスではない!

バージョンの注意:Dart 2で、このconstキーワードは常数コンテキスト内でオプショナルとなった。



オブジェクトの型を取得する (Getting an object’s type)

ランタイム中にあるオブジェクトの型を取得するには、Typeオブジェクトを返すObjectruntimeTypeプロパティを使用する。

print('The type of a is ${a.runtimeType}');



ここまでは如何にクラスたちを使用するかを見てきた。本章の残りではクラスたちをどのように実装するかを示す。



インスタンス変数 (Instance variables)

以下はどのようにインスタンス変数を宣言するかを示す:

class Point {
  num x;     // インスタンス変数xを宣言、初期値はnull
  num y;     // インスタンス変数yを宣言、初期値はnull
  num z = 0; // インスタンス変数zを宣言、初期値は0
}

初期化されていないインスタンス変数は値nullを持つ。

総てのインスタンス変数は暗示的(implicit)にゲッタ・メソッドを持つ。非finalはインスタンス変数もまた暗示的なセッタ・メソッドを持つ。詳細は「ゲッタとセッタ」の項を参照のこと。

class Point {
  num x;
  num y;
}
void main() {
  var point = Point();
  point.x = 4;             // xへのセッタ・メソッドを使用
  assert(point.x == 4);    // xへのゲッタ・メソッドを使用
  assert(point.y == null); // 値はデフォルトでnull
}

インスタンス変数をそこで宣言された場所(コンストラクタやメソッドではなく)で初期化するときは、そのインスタンスが作られた時点で、即ちコンストラクタとそのイニシャライザ・リストが実行される前に、その値がセットされる。



コンストラクタ (Constructors)

そのクラス(加えてオプショナルなものとして指名コンストラクタ(Named constructors)のなかで記されているように追加的な識別子)と同じ名前を持った関数を作ることでコンストラクタを宣言する。コンストラクタの最も一般的な書式である生成的コンストラクタ(generative constructor)は、あるクラスの新しいインスタンスを生成する:

class Point {
  num x, y;
  Point(num x, num y) {
    // こうするよりもベターな方法がある、それはのちほど
    this.x = x;
    this.y = y;
  }
}

thisというキーワードは現行のインスタンスのことを言っている。

注意:名前がかち合っている場合にのみthisを使うこと。Dartのスタイルではthisは省かれている

コンストラクタの引数にインスタンス変数を指定するのは極めて一般的で、Dartではそれを簡単化する糖衣構文(syntactic sugar)を持っている:

class Point {
  num x, y;
  // xyをセットするための糖衣構文
  // コンストラクタのボディが走る前に
  Point(this.x, this.y);
}



デフォルト・コンストラクタ (Default constructors)

コンストラクタを宣言しないときはデフォルトのコンストラクタが用意されている。デフォルト・コンストラクタは引数を持たず、そのスーパークラスの中にある引数なしのコンストラクタを呼び出す。



コンストラクタは継承されない (Constructors aren’t inherited)

サブクラスたちは自分たちのスーパークラスからコンストラクタを継承しない。コンストラクタを宣言していないサブクラスはデフォルトのコンストラクタ(引数も名前もない)のみを有する。



指名コンストラクタ (Named constructors)

あるクラスに対し複数のコンストラクタを実装するとか、より明確性を持たせる為に指名コンストラクタを使う:

class Point {
  num x, y;
  Point(this.x, this.y);
  // 指名コンストラクタ
  Point.origin() {
    x = 0;
    y = 0;
  }
}

コンストラクタたちは継承されないことを思い出して欲しい。即ちスーパークラスの指名コンストラクタはサブクラスによって継承されない。もしそのスーパークラスで定義された指名コンストラクタを持ったサブクラスを生成したいときは、そのサブクラス内でそのコンストラクタを実装しなければならない。



非デフォルトのスーパークラスのコンストラクタの呼び出し (Invoking a non-default superclass constructor)

デフォルトではサブクラス内のコンストラクタはそのスーパークラスの名前のない引数なしのコンストラクタを呼び出す。このスーパークラスのコンストラクタはそのコンストラクタのボディの開始前に呼ばれる。もし初期化子リスト(initializer list)が使われている場合は、それはスーパークラスが呼ばれる前にそれが実行される。纏めると実行順は以下のようになる:

  1. 初期化リスト

  2. スーパークラスの引数なしのコンストラクタ

  3. メインのクラスの引数なしのコンストラクタ

もしそのスーパクラスは名前なしで引数なしのコンストラクタを持っていないときは、そのスーパークラス内のコンストラクタたちひとつをマニュアルで呼ばねばならない。コロン(:)のあとでコンストラクタのボディ(もしあれば)のちょうど前でそのスーパークラスのコンストラクタを指定する。

以下の例では、EmployeeクラスのコンストラクタはそのスーパークラスであるPersonの名前付きコンストラクタを呼んでいる。DartPadでこれを実行してみると良い。

class Person {
  String firstName;
  Person.fromJson(Map data) {
    print('in Person');
  }
}
class Employee extends Person {
  // Personはデフォルトのコンストラクタを持っていない
  // super.fromJson(data)を呼ばねばならない
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}
main() {
  var emp = new Employee.fromJson({});
  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

スーパークラスのコンストラクタへの引数はそのコンストラクタを呼び出す前に計算されるので、引数は関数呼び出しのようなある式になる得る:

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

警告:スーパークラスのコンストラクタへの引数はthisへのアクセスを持たない。例えば、引数たちはstaticメソッドたちを呼べるがインスタンス・メソッドたちは呼べない。



初期化リスト (Initializer list)

コンストラクタではイニシャライザ・リスト(initializer list)とボディを使って多様な初期化が可能である。イニシャライザ・リストはコロン(':')で始まり、カンマ(',')で分離されたイニシャライザたちのリストで構成される。スーパークラスのコンストラクタを呼び出すことの他にも、イニシャライズ・リストはボディ部の前に定義すべきフィールドたちの値をセットするのに使われる。これはfinalなフィールドには必要である。そのコンストラクタのボディが走る前にインスタンス変数たちを初期化することができる。

// イニシャライザ・リストがコンストラクタ・ボディが走る前に
// インスタンス変数たちをセットする
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

警告:初期化子の右側はthisへのアクセスを持たない。

開発中に初期化リストの中でassertを使って入力を検証できる。

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

初期化子リストはfinalなフィールドを設定するのに手頃なものである。以下のコードは初期化子リストの中の3つのfinalフィールドを初期化している:

import 'dart:math';
class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;
  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);

これをDartPad等で実行すると3.605551275463989が表示される:

もう一つの例を示そう。ここでは初期化リストを使った原点設定のためのPoint.zeroというコンストラクタと極座標で初期化するPoint.polarというコンストラクタが使われている:

import 'dart:math';
class Point {
  num x, y;
  Point(this.x, this.y);
  Point.zero()
      : x = 0,
        y = 0;
  Point.polar(num theta, num radius) {
    x = cos(theta) * radius;
    y = sin(theta) * radius;
  }
}
main() {
  var myPoint = Point.polar(pi / 4.0, 1.0);
  print('${myPoint.x}, ${myPoint.y}');
}

これをDartPad等で実行すると0.7071067811865476, 0.7071067811865475が表示される。



リダイレクト・コンストラクタ (Redirecting constructors)

あるコンストラクタの唯一の目的が同じクラスの中の別のコンストラクタにリダイレクト(転送)するためという場合がある。リダイレクト・コンストラクタのボディは空で、コロン(:)のあとに出てくるコンストラクタ呼び出しがある。

class Point {
  num x, y;
  // このクラスのメインのコンストラクタ
  Point(this.x, this.y);
  // メインのコンストラクタに委譲
  Point.alongXAxis(num x) : this(x, 0);
}
main() {
  var myPoint = Point.alongXAxis(3.14);
  print(myPoint.x);
}

この例ではX軸上のPointなので、Y軸は0である。従ってPoint.alongXAxis(num x)というコンストラクタは引数y0にしてメインのコンストラクタを呼び出している。



常数コンストラクタ (Constant constructors)

そのクラスが決して変化しないオブジェクトを作るときは、それらのオブジェクトをコンパイル時常数にできる。そうするには、constコンストラクタを定義し、総てのインスタンス変数をfinalとする。

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);
  final num x, y;
  const ImmutablePoint(this.x, this.y);
}

常数コンストラクタは必ずしも常に常数を作るわけではない。詳細は「コンストラクタを使う」の節を参照のこと。



ファクトリ・コンストラクタ (Factory constructors)

Dartではファクトリ・コンストラクタ(Factory Constructor)と呼ばれる新しい種類のコンストラクタが導入されているJavaではファクトリは一般的に使われるデザイン・パタンとなっている)

ファクトリ・コンストラクタは、必ずしも常にそのクラスの新規インスタンスを生成するわけではないコンストラクタを実装する際にfactoryキーワードを付す。ファクトリ・コンストラクタは例えばあるキャッシュからあるインスタンスを返すとかある副型のインスタンスを返すとかに使われる。

ファクトリは一見staticなメソッドのように見えるが、明示的に指定したクラスのインスタンスを返すという点で異なっている。ファクトリはコンストラクタのように呼び出すことができ、そのメソッドのボディは返されるインスタンスを制御できる。ファクトリは他の言語でのコンストラクタに関わる弱点に対処している。ファクトリは新規に割り当てられたものではないインスタンスを生成できる:即ちこれらのインスタンスはキャッシュから得られる。同様にまた、ファクトリは異なったクラスのインスタンスを返すことが出来る。また、そのクラスのインスタンスを初期化するコードから分離して追加の処理コードが必要になるときにもこのコンストラクタは使用できる。

以下の例はあるキャッシュからオブジェクトたちを返すファクトリ・コンストラクタのデモである:

class Logger {
  final String name;
  bool mute = false;
  // _cacheはその名前がアンダスコアで始まっているのでライブラリ-プライベートである
  static final Map<String, Logger> _cache =
      <String, Logger>{};
  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }
  Logger._internal(this.name);
  void log(String msg) {
    if (!mute) print(msg);
  }
}
  • nameというString型の変数はfinal宣言されているので、Symbolクラスのインスタンスに固有の名前が与えられることになる。

  • インスタンスたちのキャッシュはMapで構成され、各インスタンスは名前付きで出し入れされる。このキャッシュの名前は_cacheとアンダスコアが先行しているのでprivateとなっている。

  • ファクトリはfactory Logger(String name)というシグネチュアになっている。デフォルト(名前付きでない)のコンストラクタにfactoryというプレフィックスが付いていることで、Dartはそれはファクトリ・コンストラクタであることを知る。このボディ部では以下のことが行われる:

    • キャッシュがまだ存在しない、即ちnullの状態なら、要素が空のマップとする。

    • キャッシュが存在しているなら、そのキャッシュに指定された名前のインスタンスが存在するかを調べる

      • もし存在するなら、そのインスタンスをとりだして返す。

      • 存在しないならこのクラスのインスタンスを内部コンストラクタにより生成し、それを名前付きでキャッシュにストアするとともに、そのインスタンスを返す。

  • これにより、キャッシュには指定された名前のインスタンスが必ずひとつストアされる。

例えば呼び出し側で次のような2つのインスタンスを同じ名前で生成したとする:

var a = new Logger('something');
var b = new Logger('something');

最初のaというインスタンスは新しいインスタンスを生成したものであるが、bというインスタンスはキャッシュされていたものである。プログラム開発の最初の段階では通常のコンストラクタを使っていて、必要に応じファクトリを用意すれば良い。呼び出し側のコードはその為に変更しなくても良い。

注意:ファクトリ・コンストラクタはthisへのアクセスを持たない。

他のコンストラクタと同じようにファクトリ・コンストラクタが呼び出せる:

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

次の例はGoogleの技術者のSeth Laddが示しているDartにおけるシングルトン・パタンである

class Singleton {
  static final Singleton _singleton = new Singleton._internal();
  factory Singleton() {
    return _singleton;
  }
  Singleton._internal();
}
//You can construct it with new
main() {
  var s1 = new Singleton();
  var s2 = new Singleton();
  print(identical(s1, s2));  // true
  print(s1 == s2);           // true
}

2行目の_singletonstaticでかつfinalなのでクラス変数であり、これはこのクラスの最初のインスタンス化で設定される。factory Singleton()というファクトリ・コンストラクタは従ってこのクラスでただひとつのオブジェクトを返す。main()のなかでnew Singleton();を呼ぶとこのファクトリ・コンストラクタが実行される。従って、s1s2といったオブジェクトは全く同じオブジェクトとなる。



メソッド (Methods)

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



インスタンス・メソッド (Instance methods)

オブジェクト上のインスタンス・メソッドはインスタンス変数とthisへアクセスできる。以下のサンプルのdistanceTo()メソッドはインスタンス・メソッドの一例である:

import 'dart:math';
class Point {
  num x, y;
  Point(this.x, this.y);
  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}



ゲッタとセッタ (Getters and setters)

ゲッタとセッタはあるオブジェクトのプロパティへの読み書きを提供する特別なメソッドである。各インスタンス変数は暗示的なゲッタ、加えて適切であればセッタを持っていることを思い出して頂きたい。getsetキーワードを使ってゲッタとセッタを実装して新たなプロパティを作ることもできる:

class Rectangle {
  num left, top, width, height;
  Rectangle(this.left, this.top, this.width, this.height);
  // rightgetset)とbottomgetのみ)のプロパティを定義
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}
void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

戻り値が指定されていないときは、そのゲッタの型はdynamicである。またセッタ/ゲッタはメソッドをオーバライド出来ず、メソッドはまたセッタ/ゲッタをオーバライドできない。ゲッタとセッタを用いて、クライアントのコードを変更することなく、インスタンス変数で始め、後でそれらのインスタンス変数をメソッドで包むことができる。

注意:増分(++)のような演算子は、ゲッタが明示的に定義されているか否かに拘わらず期待されるやり方で動作する。予期せぬ副作用を回避するために、その演算子は一時的にその値を保管しつつただ1回そのゲッタを呼び出す。

ゲッタとセッタを使ったもうひとつの例を示す:

import 'dart:math' as Math;
class D2Vector {
  // fields
  num abs, arg;
  // constructor
  D2Vector(this.abs, this.arg);
  // xVal setter / getter
  num get xVal => abs * Math.cos(arg);
  set xVal(num x) {
    num y = abs * Math.sin(arg);
    abs = Math.sqrt(x * x + y * y);
    arg = Math.atan(y / x);
  }
  // yVal setter / getter
  num get yVal => abs * Math.sin(arg);
  set yVal(num y) {
    num x = abs * Math.cos(arg);
    abs = Math.sqrt(x * x + y * y);
    arg = Math.atan(y / x);
  }
}
main(){
  D2Vector myVec = new D2Vector(1, 0);
  print('abs: ${myVec.abs} arg: ${myVec.arg} xVal: ${myVec.xVal} yVal: ${myVec.yVal}');
  myVec.yVal = 1;
  print('abs: ${myVec.abs} arg: ${myVec.arg} xVal: ${myVec.xVal} yVal: ${myVec.yVal}');
}
/*
abs: 1 arg: 0 xVal: 1 yVal: 0
abs: 1.4142135623730951 arg: 0.7853981633974483 xVal: 1.0000000000000002 yVal: 1
*/

main()のなかでは、最初にmyVecというD2Vectorのオブジェクトを絶対値1.0、角度0で生成している。次のprint行ではmyVec.xValmyVec.yValで、フィールドではない値をあたかもフィールドのように読みだしている。セットもmyVec.yVal = 1;のように、yValをセットしているが、実際はこれによりabsargを変更している。ここではxValyVal1になったので、absは√2argπ/2である。

このコードは説明のためのもので、実際には絶対値が0とかX長が0のときの処置、例えば例外をスローすることなどが必要である。



抽象メソッド (Abstract methods)

インスタンス、ゲッタ、及びセッタのメソッドはインターフェイスを定義して抽象とし、その実装は他のクラスに任せることができる。抽象メソッドは抽象クラス内にのみ存在できる。

あるメソッドを抽象とするには、メソッドのボディの代わりにセミコロン(;)を使う:

abstract class Doer {
  // インスタンス変数とメソッドを定義
  void doSomething(); // 抽象メソッドを定義
}
class EffectiveDoer extends Doer {
  void doSomething() {
    // 実装物を用意する、従ってここではこのメソッドは抽象ではない
  }
}



抽象クラス (Abstract classes)

抽象クラス(インスタンス化出来ないクラス)を定義するときはabstract修飾子を使う。抽象クラスは何らかの実装とともにインターフェイスを定義するのに有用である。自分の抽象クラスがインスタンス化可能のように見えるようにしたいときは、ファクトリ・コンストラクタを定義する。

抽象クラスはしばしば抽象メソッドを持つ。以下は抽象メソッドを持った抽象クラスの宣言例である:

// このクラスはabstractと宣言されているので、インスタンス化できない
abstract class AbstractContainer {
  // コンストラクタ、フィールド、メソッド等を定義
  void updateChildren(); // 抽象メソッド
}



暗示的インターフェイス (Implicit interfaces)

各クラスはそのクラスとそのクラスが実装しているインターフェイスのインスタンス・メンバたち総てを含んだインターフェイスが暗示的に定義されている。Bの実装を継承することなくクラスBAPIをサポートするクラスAを作りたいときは、クラスABインターフェイスを実装しなければならない。

クラスはinplements句で宣言し、次にそのインターフェイスたちが必要とするAPIたちを提供することで、ひとつ或いはそれ以上のインターフェイスを実装する。例えば:

// Persongreet()を含んだ暗示的インターフェイス
class Person {
  // インターフェイス内、しかしこのライブラリ内でのみ可視
  final _name;
  // これはコンストラクタなのでインターフェイス内ではない
  Person(this._name);
  // インターフェイス内
  String greet(String who) => 'Hello, $who. I am $_name.';
}
// Personインターフェイスを実装したImposter(替え玉)
class Impostor implements Person {
  get _name => '';
  String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}
/*
Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?
*/

このコードを理解すれば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?'という文字列が返される

以下はあるクラスが複数のインターフェイスを実装することを指定する例である:

class Point implements Comparable, Location {...}



クラスの拡張 (Extending a class)

サブクラス(副型)を生成するにはextendsを使い、そのスーパークラスを参照するにはsuperを使う:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}
class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}



メンバーたちのオーバライド (Overriding members)

サブクラスたちはインスタンス・メソッド、ゲッタ、及びセッタをオーバライドできる。意図的にあるメンバをオーバライドしていることを示すためには@overrideアノテーションを付す:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

型安全であるコードの中でメソッド・パラメタまたはインスタンス変数の型を狭めるにはcovariant(共変)キーワードが使える。



オーバライド可能な演算子 (Overridable operators)

下表に示された演算子たちはオーバライド可能である。例えば、あるVectorクラスを定義したときは、2つのベクトルを加算するのに+メソッドを定義することになろう。

<

+

|

[]

>

/

^

[]=

<=

~/

&

~

>=

*

<<

==

%

>>


注意:読者は!=がオーバライド可能演算子でないことに気が付いたかもしれない。式 e1 != e2は単に式!(e1 == e2)の糖衣構文なのである。

以下は+-の演算子をオーバライドしている例である:

class Vector {
  final int x, y;
  Vector(this.x, this.y);
  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
  // 演算子==hashCodeは示されていない。詳細は下の注記を見ること
  // ···
}
void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);
  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

もし==をオーバライドするときは、ObjecthashCodeゲッタもオーバライドしなければならない。==hashCodeをオーバライドしているサンプルは「マップ・キーの実装(Implementing map keys)の項を見ること。

一般的なオーバライドに関する更なる情報は「クラスの拡張 (Extending a class)を見て欲しい。



noSuchMethod()

あるコードが存在しないメソッドやインスタンス変数を使用おうとしたときに検出あるいは反応するために、noSuchMethod()をオーバライドすることができる:

class A {
  // noSuchMethodをオーバライドしないで存在しないメンバを使おうとすると
  // 存在しないメンバは NoSuchMethodErrorをもたらす
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

以下のひとつがtrueでない限り実装されていないメソッドを呼び出すことはできない:

  • そのレシーバがstaticかたのdynamicを持っている

  • 未実装メソッド(抽象はOK)を定義しているstatic型を持っており、そのレシーバのdynamic型がObject クラス内のものから異なっているnoSuchMethod()の実装を持っている

更なる情報は、非公式の noSuchMethod forwarding specificationを見られたい。



列挙型 (Enumerated types)

列挙型はECMA-408の第2版(Dart 1.8Genericsの前に追加された。列挙型(enumerated type)またはenumは固定数の定数値たちを表現するのに使われる。

列挙型はenumキーワードを使って宣言する:

enum Color { red, green, blue }

列挙型の中の各値はindexゲッタを持っており、これがそのenum宣言の中の値の位置を0を起点として返す。例えば、最初の値はインデックス02番目の値はインデックス1を持っている。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

そのenumの中の値の総てのリストを取得するには、そのenumvalues常数を使用する。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

列挙型はswitch文の中で使用でき、そのenumの値たちの総てを取り扱わないと警告が出てくる:

var aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default:         // これがないと警告となる
    print(aColor); // 'Color.blue'
}

列挙型には以下の制限がある:

  • enumをサブクラス、ミクスイン、または実装できない

  • enumを明示的にインスタンス化できない

更なる情報はDart Language Specificationを参照のこと。



Mixin : クラス機能を追加する (Adding features to a class: mixins)

ミクスインは複数のクラス階層の中であるクラスコードを再利用する手段である。

ミクスインを使うにはwithキーワードを使い、そのあとにひとつまたはそれ以上のミクスインの名前を書く。以下の例はミクスインを使った2つのクラスを示している:

class Musician extends Performer with Musical {
  // ···
}
class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

ミクスインの実装は、Objectを拡張したクラスを作り、コンストラクタたちは書かない。自分のミクスインが通常のクラスとして使えるようにしたい場合以外はclassの代わりにmixinキーワードを使用する。例えば:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;
  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

一部の型たちのみがそのミクスインを使えるように指定したいとき—例えば、自分が定義していないメソッドが呼び出せるようにする—には、必要なスーパークラスを指定するのにonを使用する:

mixin MusicalPerformer on Musician {
  // ···
}

バージョンに関する注意:mixinキーワードのサ ポートはDart 2.1で導入されている。それ以前の版では通常その代わりに抽象クラスを使用していた。更なるこの変更に関する情報はDart SDK changelog2.1 mixin specificationを見られたい。



クラス変数とクラス・メソッド (Class variables and methods)

クラスにわたる変数とメソッドをつくるにはstaticキーワードを使用する。



Static変数 (Static variables)

static変数(クラス変数)はクラスにわたる状態と常数を作るのに有用である:

class Queue {
  static const initialCapacity = 16;
  // ···
}
void main() {
  assert(Queue.initialCapacity == 16);
}

Static変数たちはそれが使われるまでは初期化されていない。

もう一つ例を示す:

class Alarm {
  static const fire = 1;
  static const earthquake = 2;
  static const tsunami = 3;
  fooMethod() => earthquake;
}
class CustomAlarm extends Alarm {
  barMethod() => Alarm.tsunami;  // サブクラスでも直接使えない
}
main() {
  assert(Alarm.fire == 1);  // インスタンス化しないでアクセス
  assert(Alarm().fooMethod() == 2);  // Alarmのインスタンス化
  assert(CustomAlarm().barMethod() == 3);  // CustomAlarmのインスタンス化
}

注意:このページは常数の名前にlowerCamelCaseを使うようにするというスタイル・ガイド勧告に従っている。



Staticメソッド (Static methods)

Staticメソッド(クラス・メソッド)はインスタンス上では動作せず、従ってthisへのアクセスを有さない。例えば:

import 'dart:math';
class Point {
  num x, y;
  Point(this.x, this.y);
  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}
void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

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

staticメソッドはコンパイル時常数として使える。例えば、ある常数コンストラクタにパラメタとしてstatic・メソッドを渡すことができる。



オブジェクトへの属性の付加 (Adding attributes to the object)

クラスではなくあるオブジェクトに対し動的に属性を付加したいこともあろう。DartではExpando<T>というクラスが用意されている。

例えば次の例を見てみよう:

//Expando Sample
class Person {
  String name;
  Person(this.name);
}
main() {
  var me = Person('Terry');
  var nationality = Expando();
  nationality[me] = 'Japan';
  print('${me.name} : ${nationality[me]}');
  var age = Expando();
  age[me] = {'age': 50};
  print('${me.name} : ${age[me]["age"]}');
}
/*
Terry : Japan
Terry : 50
*/

これはPersonというクラスに対してではなく、そのオブジェクトに対しnationalityとかageとかいう名前の属性を付加した例である。

対象となるオブジェクトは以下のものであってはいけない:

  • bool

  • num

  • String

  • null

付加するオブジェクトはこの例にあるように、Stringなどだけでなく、Mapなども使用できる。

JavaScriptでは総てのオブジェクトexpandだともいえるが、DartではExpandoオブジェクトを生成しなければならない。例えばJavaScriptでは:

var myObj = {}; // completely empty object
myObj.myProp = 'value';

において、myPropにある値を代入したとたんに属性であるmyPropが、例えそれが以前存在していなかったとしても動的に生成される。他の殆どの言語にはそのようなものは存在しない。



ティアオフ (Tear offs)

ティアオフとはその名の通りあるオブジェクトのメソッドを引き剥がしてクロージャ化することである。

次の例を見てみよう:

class Friendly {
  String name;
  Friendly(this.name);
  sayHi() {
    print("Hi, I'm $name!");
  }
}
main(){
  var friendly = new Friendly("Terry");
  var tearOff = friendly.sayHi; // メソッドの引き剥がし
  tearOff();
}

main()関数の中の2行目ではsayHiメソッドを引き剥がしている。そのメソッドを直接呼び出しているのではなく、それが呼ばれたときに該メソッドを呼び出すクロージャ(第1級関数)を返している。

Dartでは括弧で囲まれた引数リストが存在しないときに、それはそのメソッドを呼び出すのではなくてティアオフだと判断している。これはC#PythonJSなどと同じである。しかしながらこれは引数を持たないゲッタ等では機能しない。





前のページ

次のページ