Java デベロッパー向け Dart の紹介

1. はじめに

Dart は Flutter のプログラミング言語です。Flutter は、1 つのコードベースからネイティブにコンパイルして、モバイル、ウェブ、デスクトップ用の美しいアプリケーションを作成できる、Google の UI ツールキットです。

この Codelab では、Java デベロッパーが想定していない機能に焦点を当てながら Dart を紹介します。Dart の関数は 1 分で、スクリプトは 5 分で、アプリは 10 分で作成できます。

学習内容

  • コンストラクタを作成する方法
  • パラメータを指定するさまざまな方法
  • ゲッターとセッターを作成するタイミングと方法
  • Dart でプライバシーを保護する方法
  • Factory を作成する方法
  • Dart で関数型プログラミングが機能する仕組み
  • Dart の他の基本コンセプト

必要なもの

この Codelab の完了に必要なのはブラウザだけです。

サンプルはすべて DartPad で作成し、実行します。DartPad はブラウザベースのインタラクティブなツールであり、Dart の言語機能やコアライブラリを使用して操作できます。必要に応じて、WebStorm や IntelliJ(Dart プラグインを備えている)、Visual Studio Code(Dart Code 拡張機能を備えている)などの IDE を使用できます。

この Codelab で学びたいことは次のどれですか?

このトピックは初めてなので、簡単に概要を知りたい。 このトピックについてある程度は知っているが、復習したい。プロジェクトで使用するサンプルコードを確認したい。特定の項目に関する説明を確認したい。

2. 簡単な Dart クラスを作成する

まず、Java チュートリアルの Bicycle クラスと同じ機能を備えたシンプルな Dart クラスを作成します。Bicycle クラスには、ゲッターとセッターを持つプライベート インスタンス変数が含まれます。main() メソッドは、Bicycle をインスタンス化してコンソールに出力します。

99c813a1913dcc42.png c97a12197358d545.png

DartPad を起動する

この Codelab には、演習ごとに新しい DartPad インスタンスが用意されています。下記のリンクから新しいインスタンスを開きます。このインスタンスにはデフォルトの「Hello」のサンプルが含まれています。Codelab 全体で同じ DartPad を続けて使用できます。[Reset] をクリックすると、デフォルトのサンプルに戻りますが、作業内容は失われます。

b2f84ff91b0e1396.pngDartPad を開く

Bicycle クラスを定義する

b2f84ff91b0e1396.pngmain() 関数の上に、3 つのインスタンス変数を持つ Bicycle クラスを追加します。また、次のコード スニペットに示すように、main() からコンテンツを削除します。

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

cf1e10b838bf60ee.png 確認内容

  • このサンプルでは、Dart アナライザは、変数が null 値非許容ではないために変数を初期化する必要があることを示すエラーを生成します。このエラーを次のステップで解決します。
  • Dart の主なメソッドは main() という名前です。コマンドライン引数にアクセスする必要がある場合は、main(List<String> args) のように引数を追加します。
  • main() メソッドはトップレベルにあります。Dart では、クラスの外側でコードを定義できます。変数、関数、ゲッター、セッターはすべて、クラスの外側に配置できます。
  • 元の Java のサンプルでは、private タグを使ってプライベート インスタンス変数を宣言しますが、Dart ではこれを行いません。プライバシーについては、「読み取り専用変数を追加する」で詳しく説明します。
  • main()Bicycle のいずれも public として宣言されていません(識別子がすべてデフォルトで公開されているため)。Dart には publicprivateprotected のキーワードがありません。
  • 通常、Dart は、4 文字ではなく 2 文字のインデントを使用します。Dart の空白文字規則には、Dart 形式という便利なツールがあるため、気にする必要はありません。Dart のコード規則(Effective Dart)には、次のように記述されています。Dart の公式の空白文字処理規則は、Dart 形式が生成するものすべてに適用されます。

Bicycle コンストラクタを定義する

b2f84ff91b0e1396.png次のコンストラクタを Bicycle クラスに追加します。

Bicycle(this.cadence, this.speed, this.gear);

cf1e10b838bf60ee.png 確認内容

  • このコンストラクタには本文がありませんが、Dart では有効です。
  • 本文のないコンストラクタの末尾にセミコロン(;)を付けないと、DartPad では「関数の本文を指定する必要があります」というエラーが表示されます。
  • コンストラクタのパラメータ リストで this を使用すると、インスタンス変数に簡単に値を割り当てることができます。
  • 上記のコードは、イニシャライザ リストを使用する次のコードと同等です。
Bicycle(int cadence, int speed, int gear)
      : this.cadence = cadence,
        this.speed = speed,
        this.gear = gear;

コードの書式を設定する

Dart UI の上部にある [Format] をクリックすれば、いつでも Dart コードの書式を変更できます。再フォーマットは、両端揃えをオフにしてコードを DartPad に貼り付ける場合に特に便利です。

b2f84ff91b0e1396.png[Format] をクリックします。

Bicycle インスタンスをインスタンス化して出力する

b2f84ff91b0e1396.png次のコードを main() 関数に追加します。

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

b2f84ff91b0e1396.pngオプションの new キーワードを削除します。

var bike = Bicycle(2, 0, 1);

cf1e10b838bf60ee.png確認内容

  • new キーワードは、Dart 2 でオプションになりました。
  • 変数の値が変更されないことがわかっている場合、var の代わりに final を使用できます。
  • print() 関数は、文字列だけでなく、任意のオブジェクトを受け入れます。オブジェクトの toString() メソッドを使用して、ID を String に変換します。

サンプルを実行する

b2f84ff91b0e1396.pngこのサンプルを実行するには、DartPad ウィンドウの上部にある [Run] をクリックします。[Run] が有効になっていない場合は、このページの後半の問題セクションをご覧ください。

次の出力が表示されます。

Instance of 'Bicycle'

cf1e10b838bf60ee.png確認内容

  • エラーや警告は表示されません。これは、型推論が機能していること、var bike = で始まる文が Bicycle のインスタンスを定義するとアナライザが推論していることを意味します。

出力を改善する

「Instance of ‘Bicycle'」という出力は正しいのですが、あまり有益ではありません。すべての Dart クラスには toString() メソッドが用意されており、これをオーバーライドすると、より有用な出力が提供されます。

b2f84ff91b0e1396.png次の toString() メソッドを Bicycle クラスの任意の場所に追加します。

@override
String toString() => 'Bicycle: $speed mph';

cf1e10b838bf60ee.png 確認内容

  • @override アノテーションは、特定のメンバーを意図的にオーバーライドしていることをアナライザに伝えます。オーバーライドを適切に実行しないと、アナライザはエラーを出力します。
  • Dart では、文字列を指定するときに単一引用符または二重引用符を使用できます。
  • 文字列補間を使って、式の値を文字列リテラル内に配置します(${expression})。式が識別子の場合は、かっこをスキップできます($variableName)。
  • 太い矢印(=>)表記を使用して、1 行の関数またはメソッドを短くします。

サンプルを実行する

b2f84ff91b0e1396.png[Run] をクリックします。

次の出力が表示されます。

Bicycle: 0 mph

問題がある場合はコードをご確認ください

読み取り専用変数を追加する

オリジナルの Java のサンプルでは、読み取り専用変数として speed を定義し、これを非公開の変数として宣言してゲッターのみを提供していました。次に、同じ機能を Dart でも提供します。

b2f84ff91b0e1396.pngDartPad で bicycle.dart を開きます(または前の作業からのコピー使用します)。

Dart 識別子をそのライブラリ専用としてマークするには、その名前をアンダースコア(_)から開始します。名前を変更してゲッターを追加することで、speed を読み取り専用変数に変換できます。

speed を非公開の読み取り専用インスタンス変数にする

b2f84ff91b0e1396.pngBicycle コンストラクタで、speed パラメータを削除します。

Bicycle(this.cadence, this.gear);

b2f84ff91b0e1396.pngmain() で、Bicycle コンストラクタへの呼び出しから 2 番目(speed)のパラメータを削除します。

var bike = Bicycle(2, 1);

b2f84ff91b0e1396.png残りの speed_speed に変更します。(2 か所)

b2f84ff91b0e1396.png_speed を 0 に初期化します。

int _speed = 0;

b2f84ff91b0e1396.png次のゲッターを Bicycle クラスに追加します。

int get speed => _speed;

cf1e10b838bf60ee.png確認内容

  • 各変数は、数値であっても初期化する必要があります。それ以外の場合は、型宣言に ? を追加して null 許容として宣言する必要があります。
  • Dart コンパイラは、アンダースコアで始まる識別子にライブラリ専用ルールを適用します。一般にライブラリのプライバシーとは、識別子が定義されているファイル内(クラスだけではない)にのみ識別子が表示されることを意味します。
  • デフォルトでは、Dart はすべてのパブリック インスタンス変数に暗黙的なゲッターとセッターを提供します。ゲッターやセッターを独自に定義する必要はありません。ただし、読み取り専用変数または書き込み専用変数の適用、値のコンピューティングと検証、別の場所で値の更新を行う場合を除きます。
  • 元の Java のサンプルでは、cadencegear のゲッターとセッターが提供されていました。Dart のサンプルでは、これらの変数は明示的なゲッターとセッターを必要としないため、インスタンス変数のみを使用します。
  • bike.cadence のような単純なフィールドから始めて、後でゲッターやセッターを使用するようにリファクタリングできます。API は変わりません。つまり、フィールドの使用からゲッターとセッターの使用に切り替えることは、Dart の重大な変更ではありません。

読み取り専用インスタンス変数として speed の実装を完了する

b2f84ff91b0e1396.png次のメソッドを Bicycle クラスに追加します。

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

最後の Dart のサンプルは、元の Java に似ていますが、40 行ではなく 23 行というコンパクトなコードです。

class Bicycle {
  int cadence;
  int _speed = 0;
  int get speed => _speed;
  int gear;

  Bicycle(this.cadence, this.gear);

  void applyBrake(int decrement) {
    _speed -= decrement;
  }

  void speedUp(int increment) {
    _speed += increment;
  }

  @override
  String toString() => 'Bicycle: $_speed mph';
}

void main() {
  var bike = Bicycle(2, 1);
  print(bike);
}

問題がある場合はコードをご確認ください

3. オーバーロードではなくオプションのパラメータを使用する

次の演習では、Java チュートリアルの別のサンプルである Rectangle クラスを定義します。

Java コードでは、オーバーロード コンストラクタが示されています。これは、コンストラクタが同じ名前であってもパラメータの数や型が異なるという、Java では一般的な手法です。Dart はコンストラクタのオーバーロードをサポートしていないため、この状況を別の方法で処理します。それをこのセクションで示します。

b2f84ff91b0e1396.pngDartPad で Rectangle のサンプルを開きます

Rectangle コンストラクタを追加する

b2f84ff91b0e1396.png空のコンストラクタを 1 つ追加して、Java のサンプルの 4 つのコンストラクタをすべて置き換えます。

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

このコンストラクタは、省略可能な名前付きパラメータを使用します。

cf1e10b838bf60ee.png確認内容

  • this.originthis.widththis.height は、コンストラクタの宣言内でインスタンス変数を割り当てるために省略形を使用します。
  • this.originthis.widththis.height は、省略可能な名前付きパラメータです。名前付きパラメータは中かっこ({})で囲まれています。
  • this.origin = const Point(0, 0) 構文は、origin インスタンス変数のデフォルト値 Point(0,0) を指定します。指定するデフォルト値は、コンパイル時の定数とします。このコンストラクタは、3 つのインスタンス変数すべてにデフォルト値を指定します。

出力を改善する

b2f84ff91b0e1396.png次の toString() 関数を Rectangle クラスに追加します。

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

コンストラクタを使用する

b2f84ff91b0e1396.pngmain() を次のコードに置き換えて、必要なパラメータのみを使用して Rectangle をインスタンス化できることを確認します。

main() {
  print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(Rectangle(origin: const Point(10, 10)));
  print(Rectangle(width: 200));
  print(Rectangle());
}

cf1e10b838bf60ee.png確認内容

  • Rectangle の Dart コンストラクタは、1 行のコードです。Java バージョンの同等のコンストラクタには 16 行のコードがあります。

サンプルを実行する

次の出力が表示されます。

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0

問題がある場合はコードをご確認ください

4. Factory を作成する

Factory は、Java で一般的に使用される設計パターンです。Factory は、インスタンス化の細部を非表示にする機能、Factory の戻り値の型がサブタイプを返す機能、必要に応じて新しいオブジェクトではなく既存のオブジェクトを返す機能など、オブジェクトの直接インスタンス化に比べて多くの利点があります。

このステップでは、図形作成 Factory を実装する 2 つの方法について説明します。

  • オプション 1: 最上位関数を作成する。
  • オプション 2: Factory コンストラクタを作成する。

この演習では、Shape のサンプルを使用し、図形をインスタンス化して計算領域を出力します。

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

main() {
  final circle = Circle(2);
  final square = Square(2);
  print(circle.area);
  print(square.area);
}

b2f84ff91b0e1396.pngDartPad で Shape のサンプルを開きます

コンソール領域に、次のような円と正方形の計算領域が表示されています。

12.566370614359172
4

cf1e10b838bf60ee.png確認内容

  • Dart は抽象クラスをサポートしています。
  • 1 つのファイルで複数のクラスを定義できます。
  • dart:math は Dart のコアライブラリの 1 つです。その他のコアライブラリには、dart:coredart:asyncdart:convertdart:collection などがあります。
  • 通常、Dart ライブラリの定数は lowerCamelCase です(たとえば、PI) ではなく pi です)。この理由について詳しくは、定数名には lowerCamelCase を優先的に使用するのスタイル ガイドラインをご覧ください。
  • 次のコードは、値を計算する 2 つのゲッター(num get area => pi * pow(radius, 2); // Circle num get area => pow(side, 2); // Square)を示しています。

オプション 1: 最上位関数を作成する

b2f84ff91b0e1396.png最上位で次の関数をクラスの外側に追加して、Factory をトップレベル関数として実装します。

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}

b2f84ff91b0e1396.pngmain() メソッドの最初の 2 行を置き換えて、factory 関数を呼び出します。

  final circle = shapeFactory('circle');
  final square = shapeFactory('square');

サンプルを実行する

出力は前と同じです。

cf1e10b838bf60ee.png確認内容

  • 'circle' または 'square' 以外の文字列でこの関数が呼び出されると、例外がスローされます。
  • Dart SDK では、多くの一般的な例外に対応するクラスが定義されています。あるいは、Exception クラスを実装してより具体的な例外を生成することや、(このサンプルのように)発生した問題を表す文字列をスローすることが可能です。
  • 例外が発生すると、DartPad は Uncaught を報告します。より有用な情報を表示するには、コードを try-catch 文でラップし、例外を出力します。必要に応じて、こちらの DartPad のサンプルをご覧ください。
  • 文字列内で単一引用符を使用するには、スラッシュを使用して埋め込まれた引用符をエスケープするか('Can\'t create $type.')、二重引用符を使用して文字列を指定します("Can't create $type.")。

問題がある場合はコードをご確認ください

オプション 2: Factory コンストラクタを作成する

Dart の factory キーワードを使用して、Factory コンストラクタを作成します。

b2f84ff91b0e1396.pngFactory コンストラクタを抽象 Shape クラスに追加します。

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    throw 'Can\'t create $type.';
  }
  num get area;
}

b2f84ff91b0e1396.pngmain() の最初の 2 行を次のコードに置き換えて、図形をインスタンス化します。

  final circle = Shape('circle');
  final square = Shape('square');

b2f84ff91b0e1396.png以前追加した shapeFactory() 関数を削除します。

cf1e10b838bf60ee.png 確認内容

  • Factory コンストラクタのコードは、shapeFactory() 関数で使用されるコードと同じです。

問題がある場合はコードをご確認ください

5. インターフェースを実装する

すべてのクラスがインターフェースを定義するため、Dart 言語には interface キーワードが含まれていません。

b2f84ff91b0e1396.pngDartPad で Shape のサンプルを開きます(または、コピーをそのまま使用します)。

b2f84ff91b0e1396.pngCircle インターフェースを実装する CircleMock クラスを追加します。

class CircleMock implements Circle {}

b2f84ff91b0e1396.pngCircleMockCircle の実装を継承せず、そのインターフェースのみを使用するため、「具体的な実装がありません」というエラーが表示されます。このエラーを解決するには、arearadius のインスタンス変数を定義します。

class CircleMock implements Circle {
  num area = 0;
  num radius = 0;
}

cf1e10b838bf60ee.png確認内容

  • CircleMock クラスで動作が定義されていない場合でも、Dart では有効であり、アナライザはエラーを生成しません。
  • CircleMockarea インスタンス変数は、Circlearea ゲッターを実装します。

問題がある場合はコードをご確認ください

6. Dart を関数型プログラミングに使用する

関数型プログラミングでは、次のようなことが可能です。

  • 関数を引数として渡す。
  • 変数に関数を割り当てる。
  • 複数の引数を取る関数を、それぞれ 1 つの引数を取る一連の関数に分割します(カリー化とも呼ばれます)。
  • 定数値として使用できる名前のない関数を作成します。この関数はラムダ式とも呼ばれます。ラムダ式は JDK 8 リリースで Java に追加されました。

Dart はこれらの機能をすべてサポートしています。Dart では、関数もオブジェクトであり、型は Function です。つまり、関数を変数に割り当てることも、引数として他の関数に渡すこともできます。このサンプルのように、Dart クラスのインスタンスを関数として呼び出すこともできます。

次のサンプルでは、命令型(非関数形式)コードを使用しています。

String scream(int length) => "A${'a' * length}h!";

main() {
  final values = [1, 2, 3, 5, 10, 50];
  for (var length in values) {
    print(scream(length));
  }
}

b2f84ff91b0e1396.pngDartPad で Scream のサンプルを開きます

出力は次のようになります。

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

cf1e10b838bf60ee.png確認内容

  • 文字列補間を使用する場合、文字列 ${'a' * length} は「文字 'a'length 回繰り返されている」と評価されます。

命令型コードを関数型コードに変換する

b2f84ff91b0e1396.pngmain() の命令型 for() {...} ループを削除し、メソッド チェーンを使用する 1 行のコードと置き換えます。

  values.map(scream).forEach(print);

サンプルを実行する

関数型のアプローチでは、命令型サンプルと同じ 6 つの例外が出力されます。

問題がある場合はコードをご確認ください

その他の Iterable 機能を使用する

コアとなる List クラスと Iterable クラスは、fold()where()join()skip() などをサポートしています。Dart には、マッピングとセットのサポートも組み込まれています。

b2f84ff91b0e1396.pngmain()values.map() の行を次のように置き換えます。

  values.skip(1).take(3).map(scream).forEach(print);

サンプルを実行する

出力は次のようになります。

Aaah!
Aaaah!
Aaaaaah!

cf1e10b838bf60ee.png確認内容

  • skip(1) は、values リストリテラルの最初の値である 1 をスキップします。
  • take(3) は、次の 3 つの値(2、3、5)を valuesリストリテラルから取得します。
  • 残りの値はスキップされます。

問題がある場合はコードをご確認ください

7. 完了

この Codelab を完了することで、Java と Dart の違いを理解できました。Dart は学習しやすく、またコアライブラリ豊富なパッケージを備えているため、作業効率が向上します。Dart は、サイズが大きいアプリに適しています。何百人もの Google のエンジニアが、Dart を使用してミッション クリティカルなアプリを作成し、Google に大きな収益をもたらしています。

次のステップ

20 分間の Codelab では、Java と Dart の違いをすべて説明するのに十分ではありません。たとえば、この Codelab では次の内容に触れていません。

  • async または await を使用すると、非同期コードを同期コードのように記述できます。こちらの DartPad のサンプルでは、π の最初の 5 桁の計算をアニメーション化しています。
  • メソッドのカスケードでは、すべてがビルダーになります。
  • null 対応演算子

実際に動作している Dart テクノロジーについては、Flutter の Codelab をご覧ください。

詳細

Dart について詳しくは、次の記事、リソース、ウェブサイトをご覧ください。

記事

リソース

ウェブサイト