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
をインスタンス化してコンソールに出力します。
DartPad を起動する
この Codelab には、演習ごとに新しい DartPad インスタンスが用意されています。下記のリンクから新しいインスタンスを開きます。このインスタンスにはデフォルトの「Hello」のサンプルが含まれています。Codelab 全体で同じ DartPad を続けて使用できます。[Reset] をクリックすると、デフォルトのサンプルに戻りますが、作業内容は失われます。
Bicycle クラスを定義する
main()
関数の上に、3 つのインスタンス変数を持つ Bicycle
クラスを追加します。また、次のコード スニペットに示すように、main()
からコンテンツを削除します。
class Bicycle {
int cadence;
int speed;
int gear;
}
void main() {
}
確認内容
- このサンプルでは、Dart アナライザは、変数が null 値非許容ではないために変数を初期化する必要があることを示すエラーを生成します。このエラーを次のステップで解決します。
- Dart の主なメソッドは
main()
という名前です。コマンドライン引数にアクセスする必要がある場合は、main(List<String> args)
のように引数を追加します。 main()
メソッドはトップレベルにあります。Dart では、クラスの外側でコードを定義できます。変数、関数、ゲッター、セッターはすべて、クラスの外側に配置できます。- 元の Java のサンプルでは、
private
タグを使ってプライベート インスタンス変数を宣言しますが、Dart ではこれを行いません。プライバシーについては、「読み取り専用変数を追加する」で詳しく説明します。 main()
とBicycle
のいずれもpublic
として宣言されていません(識別子がすべてデフォルトで公開されているため)。Dart にはpublic
、private
、protected
のキーワードがありません。- 通常、Dart は、4 文字ではなく 2 文字のインデントを使用します。Dart の空白文字規則には、Dart 形式という便利なツールがあるため、気にする必要はありません。Dart のコード規則(Effective Dart)には、次のように記述されています。Dart の公式の空白文字処理規則は、Dart 形式が生成するものすべてに適用されます。
Bicycle コンストラクタを定義する
次のコンストラクタを
Bicycle
クラスに追加します。
Bicycle(this.cadence, this.speed, this.gear);
確認内容
- このコンストラクタには本文がありませんが、Dart では有効です。
- 本文のないコンストラクタの末尾にセミコロン(
;
)を付けないと、DartPad では「関数の本文を指定する必要があります」というエラーが表示されます。 - コンストラクタのパラメータ リストで
this
を使用すると、インスタンス変数に簡単に値を割り当てることができます。 - 上記のコードは、イニシャライザ リストを使用する次のコードと同等です。
Bicycle(int cadence, int speed, int gear)
: this.cadence = cadence,
this.speed = speed,
this.gear = gear;
コードの書式を設定する
Dart UI の上部にある [Format] をクリックすれば、いつでも Dart コードの書式を変更できます。再フォーマットは、両端揃えをオフにしてコードを DartPad に貼り付ける場合に特に便利です。
[Format] をクリックします。
Bicycle インスタンスをインスタンス化して出力する
次のコードを
main()
関数に追加します。
void main() {
var bike = new Bicycle(2, 0, 1);
print(bike);
}
オプションの
new
キーワードを削除します。
var bike = Bicycle(2, 0, 1);
確認内容
new
キーワードは、Dart 2 でオプションになりました。- 変数の値が変更されないことがわかっている場合、
var
の代わりにfinal
を使用できます。 print()
関数は、文字列だけでなく、任意のオブジェクトを受け入れます。オブジェクトのtoString()
メソッドを使用して、ID をString
に変換します。
サンプルを実行する
このサンプルを実行するには、DartPad ウィンドウの上部にある [Run] をクリックします。[Run] が有効になっていない場合は、このページの後半の問題セクションをご覧ください。
次の出力が表示されます。
Instance of 'Bicycle'
確認内容
- エラーや警告は表示されません。これは、型推論が機能していること、
var bike =
で始まる文が Bicycle のインスタンスを定義するとアナライザが推論していることを意味します。
出力を改善する
「Instance of ‘Bicycle'」という出力は正しいのですが、あまり有益ではありません。すべての Dart クラスには toString()
メソッドが用意されており、これをオーバーライドすると、より有用な出力が提供されます。
次の
toString()
メソッドを Bicycle
クラスの任意の場所に追加します。
@override
String toString() => 'Bicycle: $speed mph';
確認内容
@override
アノテーションは、特定のメンバーを意図的にオーバーライドしていることをアナライザに伝えます。オーバーライドを適切に実行しないと、アナライザはエラーを出力します。- Dart では、文字列を指定するときに単一引用符または二重引用符を使用できます。
- 文字列補間を使って、式の値を文字列リテラル内に配置します(
${expression}
)。式が識別子の場合は、かっこをスキップできます($variableName
)。 - 太い矢印(
=>
)表記を使用して、1 行の関数またはメソッドを短くします。
サンプルを実行する
[Run] をクリックします。
次の出力が表示されます。
Bicycle: 0 mph
問題がある場合は、コードをご確認ください。
読み取り専用変数を追加する
オリジナルの Java のサンプルでは、読み取り専用変数として speed
を定義し、これを非公開の変数として宣言してゲッターのみを提供していました。次に、同じ機能を Dart でも提供します。
DartPad で
bicycle.dart
を開きます(または前の作業からのコピー使用します)。
Dart 識別子をそのライブラリ専用としてマークするには、その名前をアンダースコア(_
)から開始します。名前を変更してゲッターを追加することで、speed
を読み取り専用変数に変換できます。
speed を非公開の読み取り専用インスタンス変数にする
Bicycle
コンストラクタで、speed
パラメータを削除します。
Bicycle(this.cadence, this.gear);
main()
で、Bicycle
コンストラクタへの呼び出しから 2 番目(speed
)のパラメータを削除します。
var bike = Bicycle(2, 1);
残りの
speed
を _speed
に変更します。(2 か所)
_speed
を 0 に初期化します。
int _speed = 0;
次のゲッターを
Bicycle
クラスに追加します。
int get speed => _speed;
確認内容
- 各変数は、数値であっても初期化する必要があります。それ以外の場合は、型宣言に
?
を追加して null 許容として宣言する必要があります。 - Dart コンパイラは、アンダースコアで始まる識別子にライブラリ専用ルールを適用します。一般にライブラリのプライバシーとは、識別子が定義されているファイル内(クラスだけではない)にのみ識別子が表示されることを意味します。
- デフォルトでは、Dart はすべてのパブリック インスタンス変数に暗黙的なゲッターとセッターを提供します。ゲッターやセッターを独自に定義する必要はありません。ただし、読み取り専用変数または書き込み専用変数の適用、値のコンピューティングと検証、別の場所で値の更新を行う場合を除きます。
- 元の Java のサンプルでは、
cadence
とgear
のゲッターとセッターが提供されていました。Dart のサンプルでは、これらの変数は明示的なゲッターとセッターを必要としないため、インスタンス変数のみを使用します。 bike.cadence
のような単純なフィールドから始めて、後でゲッターやセッターを使用するようにリファクタリングできます。API は変わりません。つまり、フィールドの使用からゲッターとセッターの使用に切り替えることは、Dart の重大な変更ではありません。
読み取り専用インスタンス変数として speed の実装を完了する
次のメソッドを
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 はコンストラクタのオーバーロードをサポートしていないため、この状況を別の方法で処理します。それをこのセクションで示します。
DartPad で Rectangle のサンプルを開きます。
Rectangle コンストラクタを追加する
空のコンストラクタを 1 つ追加して、Java のサンプルの 4 つのコンストラクタをすべて置き換えます。
Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});
このコンストラクタは、省略可能な名前付きパラメータを使用します。
確認内容
this.origin
、this.width
、this.height
は、コンストラクタの宣言内でインスタンス変数を割り当てるために省略形を使用します。this.origin
、this.width
、this.height
は、省略可能な名前付きパラメータです。名前付きパラメータは中かっこ({}
)で囲まれています。this.origin = const Point(0, 0)
構文は、origin
インスタンス変数のデフォルト値Point(0,0)
を指定します。指定するデフォルト値は、コンパイル時の定数とします。このコンストラクタは、3 つのインスタンス変数すべてにデフォルト値を指定します。
出力を改善する
次の
toString()
関数を Rectangle
クラスに追加します。
@override
String toString() =>
'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';
コンストラクタを使用する
main()
を次のコードに置き換えて、必要なパラメータのみを使用して 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());
}
確認内容
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);
}
コンソール領域に、次のような円と正方形の計算領域が表示されています。
12.566370614359172
4
確認内容
- Dart は抽象クラスをサポートしています。
- 1 つのファイルで複数のクラスを定義できます。
dart:math
は Dart のコアライブラリの 1 つです。その他のコアライブラリには、dart:core
、dart:async
、dart:convert
、dart:collection
などがあります。- 通常、Dart ライブラリの定数は
lowerCamelCase
です(たとえば、PI)
ではなくpi
です)。この理由について詳しくは、定数名には lowerCamelCase を優先的に使用するのスタイル ガイドラインをご覧ください。 - 次のコードは、値を計算する 2 つのゲッター(
num get area => pi * pow(radius, 2); // Circle num get area => pow(side, 2); // Square
)を示しています。
オプション 1: 最上位関数を作成する
最上位で次の関数をクラスの外側に追加して、Factory をトップレベル関数として実装します。
Shape shapeFactory(String type) {
if (type == 'circle') return Circle(2);
if (type == 'square') return Square(2);
throw 'Can\'t create $type.';
}
main()
メソッドの最初の 2 行を置き換えて、factory 関数を呼び出します。
final circle = shapeFactory('circle');
final square = shapeFactory('square');
サンプルを実行する
出力は前と同じです。
確認内容
'circle'
または'square'
以外の文字列でこの関数が呼び出されると、例外がスローされます。- Dart SDK では、多くの一般的な例外に対応するクラスが定義されています。あるいは、
Exception
クラスを実装してより具体的な例外を生成することや、(このサンプルのように)発生した問題を表す文字列をスローすることが可能です。 - 例外が発生すると、DartPad は
Uncaught
を報告します。より有用な情報を表示するには、コードをtry-catch
文でラップし、例外を出力します。必要に応じて、こちらの DartPad のサンプルをご覧ください。 - 文字列内で単一引用符を使用するには、スラッシュを使用して埋め込まれた引用符をエスケープするか(
'Can\'t create $type.'
)、二重引用符を使用して文字列を指定します("Can't create $type."
)。
問題がある場合は、コードをご確認ください。
オプション 2: Factory コンストラクタを作成する
Dart の factory
キーワードを使用して、Factory コンストラクタを作成します。
Factory コンストラクタを抽象
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;
}
main()
の最初の 2 行を次のコードに置き換えて、図形をインスタンス化します。
final circle = Shape('circle');
final square = Shape('square');
以前追加した
shapeFactory()
関数を削除します。
確認内容
- Factory コンストラクタのコードは、
shapeFactory()
関数で使用されるコードと同じです。
問題がある場合は、コードをご確認ください。
5. インターフェースを実装する
すべてのクラスがインターフェースを定義するため、Dart 言語には interface
キーワードが含まれていません。
DartPad で Shape のサンプルを開きます(または、コピーをそのまま使用します)。
Circle
インターフェースを実装する CircleMock
クラスを追加します。
class CircleMock implements Circle {}
CircleMock
は Circle
の実装を継承せず、そのインターフェースのみを使用するため、「具体的な実装がありません」というエラーが表示されます。このエラーを解決するには、area
と radius
のインスタンス変数を定義します。
class CircleMock implements Circle {
num area = 0;
num radius = 0;
}
確認内容
CircleMock
クラスで動作が定義されていない場合でも、Dart では有効であり、アナライザはエラーを生成しません。CircleMock
のarea
インスタンス変数は、Circle
のarea
ゲッターを実装します。
問題がある場合は、コードをご確認ください。
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));
}
}
出力は次のようになります。
Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!
確認内容
- 文字列補間を使用する場合、文字列
${'a' * length}
は「文字'a'
がlength
回繰り返されている」と評価されます。
命令型コードを関数型コードに変換する
main()
の命令型 for() {...}
ループを削除し、メソッド チェーンを使用する 1 行のコードと置き換えます。
values.map(scream).forEach(print);
サンプルを実行する
関数型のアプローチでは、命令型サンプルと同じ 6 つの例外が出力されます。
問題がある場合は、コードをご確認ください。
その他の Iterable 機能を使用する
コアとなる List
クラスと Iterable
クラスは、fold()
、where()
、join()
、skip()
などをサポートしています。Dart には、マッピングとセットのサポートも組み込まれています。
main()
の values.map()
の行を次のように置き換えます。
values.skip(1).take(3).map(scream).forEach(print);
サンプルを実行する
出力は次のようになります。
Aaah!
Aaaah!
Aaaaaah!
確認内容
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 について詳しくは、次の記事、リソース、ウェブサイトをご覧ください。
記事
リソース
ウェブサイト