Flutter開発のためのDart入門(7)クラス②

Dart_programming_language_logo

前回の投稿に続き、今回もDartについて解説していきます。
引き続きクラスについて解説します。

抽象クラス

抽象クラスとは継承されることを前提として、処理内容を記述しないメソッド(抽象メソッド)を含むクラスです。
抽象クラス単体ではインスタンス化することはできません。

反対にインスタンス化できるクラスは具象クラスと呼びます。
抽象クラスを定義する際はabstractキーワードを指定します。

abstract class 抽象クラス名 {
}

抽象メソッド

抽象クラスには抽象メソッドを定義できます。

抽象メソッドには処理内容を記述できません。

メソッド名()の後ろの{処理内容}の代わりにセミコロン;を記述します。

abstract class Shape {
  double getArea(); // 抽象メソッド
}

継承

既存のクラスを元に新たなクラスを定義することを継承と呼びます。
また、継承において、元となるクラスをスーパークラス、新たに定義されるクラスをサブクラスと呼びます。
サブクラスを定義する際には、extendsキーワードを使用します。

次のコードは先程の抽象クラスを継承した例です。

abstract class Shape { // 抽象クラス
  double getArea(); // 抽象メソッド
}

// 抽象クラスを継承
class Rectangle extends Shape {
  double width = 0;
  double height = 0;

  Rectangle(this.width, this.height);

  // 抽象メソッドを実装
  double getArea() {
    return this.width * this.height;
  }
}

void main() {
  var rect = Rectangle(3, 4);
  print('四角形の面積は${rect.getArea()}㎠');
}

実行結果

四角形の面積は12㎠

※Dartでは総てがオブジェクトです。Javaのプリミティブ型(intやbooleanなどのデータ型)のようなものは存在しません。すべてのオブジェクトは Objectクラスを継承しています。

オーバーライド

オーバーライドとは、スーパークラスで定義しているメソッドを、同じ名前でサブクラスで再定義することです。
スーパークラスで定義したメソッドと目的は同じであるが、処理が異なるメソッドを定義する場合に使用します。
@overrideアノテーションを使用して、意図的にメソッドをオーバーライドしていることを示すことができます。

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

暗黙的インターフェイス

インターフェースとは

インターフェースとは簡単に言うと、公開されたクラスの取り決めのことです。
ほとんどのインターフェイスは処理内容を含まない抽象クラスから作成されます。
DartにはJavaのように明示的にインターフェイスを宣言するinterfaceキーワードはありません。

インターフェースを実装する場合、extendsではなくimplementsキーワードを使用します。

// 抽象クラスA
abstract class クラスA {
  void someMethod();
}

// クラスAインターフェースの実装
class クラスB implements クラスA {
    void someMethod() {
        処理内容
    }
}

※JavaやKotlinのinterface、Swiftのprotocolにあたります。

暗黙的インターフェース

前述の通り、Dartには明示的なインターフェースはありません。
代わりに、任意のクラスをインターフェースとして使用できます。
これはすべてのクラスにあらかじめ暗黙的にインターフェースが定義されているためです。

次のコードではImpostorクラスはPersonクラスを実装(implements)しています。

ImpostorクラスはPersonクラスのインターフェースをすべて実装する必要があります。

// 暗黙的インターフェイスPersonにはgreet()メソッドが含まれています
class Person {
  final String _name;

  Person(this._name);

  String greet(String who) => 'Hello, $who. I am $_name.';
}

// Personインターフェースの実装
class Impostor implements Person {
  String 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?

列挙型

列挙型とはいくつかの定数をひとまとまりにして扱えるようにしたもので、プログラマが任意に定義できます。

enumキーワードを使用して列挙型を宣言します。

次のコードはswich文で列挙型を使ったコードです。

※列挙型のすべての値を条件に含めないと警告が出ます。

enum Color { red, green, blue }

void main() {
  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

クラス変数

クラス変数やクラスメソッドは、クラス全体で扱うデータを格納するために使用します。

クラス変数、クラスメソッドはそれぞれstatic変数、staticメソッドとも呼ばれます。
呼び出す際には、クラス名.static変数名クラス名.staticメソッド名()で呼び出します。
実装する際はstaticキーワードを指定します。

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

クラスメソッド

クラスメソッドはインスタンス上では動作しないので、thisにアクセスすることはできません。

しかし、クラス変数にはアクセスできます。次の例では、クラス上で直接スタティックメソッドを呼び出しています。

import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  static double 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);
}

実行結果

2.8284271247461903

ジェネリクス(総称型)

以前、List型の解説でList<String>のように<>内に型を指定していました。
ジェネリクス型でクラス定義をおこなうことにより、クラス内で型を事前に決めるのではなく、クラス利用時に決定できます。
次のコードはmain()関数内で指定したString型の値とint型の値を同じクラス定義であつかっています。

class GenericMember<T> {
  final List<T> _memberList = [];
  void push(T item) => _memberList.add(item);
  T pop() => _memberList.removeLast();
  get memberList => _memberList;
}

void main() {
  final memberA = GenericMember<String>();
  memberA.push("Ken");
  memberA.push("Bob");
  //memberA.push(1);
  print(memberA.memberList);

  final memberB = GenericMember<int>();
  memberB.push(1);
  memberB.push(2);
  memberB.push(3);
  print(memberB.memberList);
  memberB.pop();
  print(memberB.memberList);
}

実行結果

[Ken, Bob]
[1, 2, 3]
[1, 2]

クラス名に続く<>はダイヤモンド演算子と呼ばれており、Tのことを型パラメータといいます。

<T>の他に<E>や<K, V>などがあり、これらは汎用的な型、つまり、仮の型を使用することを表すために使用します。

型パラメータの名前は任意でかまいませんが、通常は意味のある大文字1文字が使用されます。
※TはType、EはElement、KはKey、VはValueの頭文字を意味しています

ジェネリクス型を使用することによりシンプルで安全なコードが実現できます。

たかひら かずま
ウォーキングをしながら音楽を聴いたり考えごとをするのが好きです。
最近リモートワークで太ってしまったのでダイエットに挑戦中!

Egg Device Application

東京品川のスマホアプリ製作・開発会社です。
一般アプリ業務用アプリからVRアプリまで開発可能。

求人情報

スマホアプリ製作・開発の
相談を受け付けています

メールでのご相談

お電話でのご相談
TEL 03-5422-7524
平日10:00~18:00