この記事について
この資料は株式会社Diverseの社内で使用しているDartを速習したい人向けの教材(2018/08/09作成)を公開したものです。
社内でのFlutter採用が本決まりになったため関係エンジニア向けに作成したものですが、多くの人に役立ててもらえる可能性があるため公開と相成りました。
Diverseは今後もコミュニティに貢献する活動を続けてゆきます。
気になった方は @kikuchy または弊社社員へお気軽にご連絡ください。
まえがき
Kotlinより断然Javaに近い言語なので、Javaベースで話した方が早い箇所が多そう。
以下の前提の人がFlutter開発で不便しない程度にDartを読み書きできるようになることを目標としています。
- 解説するのはDart2
- Java/KotlinをAndroidアプリ開発で一通り使ったことがある
- JavaScriptやRubyもかじったことがある
関数/変数スコープ
Kotlinと同じくトップレベルに関数宣言、変数宣言することができます。
mainの宣言もKotlinと同じくトップレベル。
不定型
Javaの Object
、Kotlinの Any
と同じく、「何型であってもよい」型は dynamic
と表記しますできます。
Effective Dartによると、「本当に何型であっても良い」ことを示す際には Object
を、「型Aと型Bのうちどちらか、のような、期待する型が決まってはいるがDartの型システムで表現できない型表現」を dynamic
でするべき、らしいです。
/// [arg]を真偽値型に変換する。[arg]はStringかboolじゃないとダメ。
bool convertToBool(dynamic arg) {
if (arg is bool) return arg;
if (arg is String) return arg == 'true';
throw ArgumentError('Cannot convert $arg to a bool.');
}
可視性
_
から始まるメンバ関数/変数が、Javaで private
キーワードをつけたメソッド/フィールドに相当します。
クラスも _
から始める名前にできます。
パッケージプライベートはなく、ライブラリプライベートしか存在しないようです。
また、同じライブラリ内であれば _
で始まるフィールドもクラス外から触れてしまうので、無法者が現れた場合を考慮するならlinterなどで治安維持するしかないかも… 😢
型推論
変数の型とか関数の引数とかあちこち推論してくれるみたい。
Kotlinと同程度は推論してくれるとみて良さそう。
var hoge = "huga";
[1, 2, 3].forEach((x) => print(x*2));
これで hoge
は String
型、 x
は int
型と推論されます。
型を指定する場合は var
の前とか final
の後とかに型名を書きます。
(ただし var
は省略が可能。省略すると、Javaの変数定義と同じ書き方になる)
変数のデフォルト値
null
です。なんてこった。
全ての変数は null
を代入できるので、nullチェックは自分で行わないといけません。なんてこった。
(ただ、今後バージョンアップでのNull安全型の導入が決まっているので、将来的にはその必要が無くなりそうです)
Dart 2.7でNNBD (Non null by default)がプレビュー導入されました!
ちなみに assert()
メソッドはproduction環境だとスルーされるようなので、「ある変数や引数にnullが入っていることを期待しない」系はassertで弾くと良いかと思います。
NNBD
Dart 2.7でプレビュー導入された機能です。
Dart 2.12でstableになりました。
Kotlinと似たような記法で、型レベルでnull安全かそうでないかを識別できます。
// これはnon-null
final int a = 1;
// なのでこれはエラー
final int b = null;
// nullableであることを示すには型名に ? をつける
final int? c = null;
// nullableな型をnon-nullな型にキャストするには ! をつける。nullに ! を付けるとKotlin同様ランタイムエラーになる
final int d = c!;
また、これに伴ってか required
キーワードが名前付き任意パラメータで使えるようになりました。
required
をつけると、名前付きであるものの必須なパラメータをとることができます。
// arg1 はnon-nullなString型
void doSomething({required String arg1}) { ... }
// arg1 を省略しようとするとエラー
doSomething(arg1: "hoge");
また、 late
キーワードを使うと、Kotlinの lateinit var
と同様に初期化のチェックを遅らせることも、 lazy
delegateと同じように、初期化をメンバ呼び出し時まで遅延することもできます。
// 初期値を指定しなければ late init var と同じような挙動に。
late TextEditingController _textEditing controller;
// 初期値を指定すれば by lazy {} と同じような挙動に。
late _animationController = AnimationController(vsync: this);
pubspec.yaml
の environment.sdk
の下限を 2.12
以上にすることでNNBDを有効にできます。
environment:
sdk: ">=2.12.0 <3.0.0"
再代入不能変数と定数と不変オブジェクト
Javaの final
、Kotlinの val
と同じような機能が似たような感じで使えます。
final hoge = "fuga";
var
の代わりに final
を使うと再代入不能変数になります。
ただし、厳密にJavaの final
(一度代入したら、その後は再代入不可)と同じわけではなく、初期化できるタイミングが限られています。
クラスフィールドに final
を使った場合には、後述の初期化子リストで初期化しないといけません。
特殊なときじゃないとセッターにアクセスできないフィールドみたいなイメージ?
定数は const
で宣言します。これも型推論してくれます。
こちらはKotlinの const val
と同じく、コンパイル時に値が決まっていないといけません。
const CONSTANT = "VALUE";
const
はリスト系リテラルや値クラスにも使えます。
const
宣言された値はメンバを変更できません。(と言っても変更すると例外が飛ぶだけでコンパイル時エラーにはしてくれない。なんてこった)
// 変数は再代入可能だが、中のリストは変更不可
var hoge = const [1, 2, 3];
組み込み型の説明
数値型
整数型 int
, BigInt
と浮動小数点型 double
しかないっぽいです。
int
と double
は num
の小クラスのようですが、num
は dart:math
パッケージを使うときくらいしか意識することないと思います。
また、 int
は64ビット、2の補数表現なので、それ以上の範囲の数値を扱う場合は BigInt
を使うことになるかと思います。
リテラルの数値はコンパイル時に解決されるので、 const
初期化子は計算式(定数式)でも良いっぽいです。
const A = 3;
const B = 9;
const C = A * B;
文字列
String var str = "こんなふうに"
'改行で続けても'
"一つの文字列に結合されます。";
str = "JSよろしく" + "加算演算子でも結合が可能です";
str = "Kotlinと同様に $exprName で変数の内容を文字列中に展開できます";
str = """
ダブルクォートもしくはシングルクォートを3つ続けると
複数行文字列リテラルになります。
改行も反映されます。
""";
真偽値
Javaのプリミティブ boolean
と同じような bool
があります。以上。
ただし bool
型変数には null
を入れられてしまうので、初期化時にJavaと同じ気分でいるとバグを作り込む原因になるので注意。
リスト
Dartに配列はありません。リストがあるだけです。
APIはJavaのListとほぼ同じっぽいですが、Dartにはリストを作るリテラルがあります。
// これで List<int> になる
final list = [1, 3, 5, 7, 9];
print(list[1]); // 3
Dart 2.3から、リストの中にリストを展開するスプレッド記法もあります。
final listA = [1, 2, 3, 4];
final listB = [...listA, 5, 6, 7, 8, 9];
print(listB); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
リストリテラル内に要素を追加させる/させないの条件式を書けるCollection-Ifという機能もあります。
こちらの機能もDart 2.3から。
final list = [
if (true) 1,
if (false) 2,
3
];
print(list); // [1, 3]
何らかの繰り返しの結果をリストにするCollection-Forという機能もあります。
こちらもDart 2.3から
final listA = [1, 2, 3, 4];
final listB = [
for (var a in listA) a * 2,
];
print(listB); // [2, 4, 6, 8]
マップ
APIはJavaのMapとほぼ同じっぽいですが、Dartにはマップを作るリテラルがあります。
var map = {
"ほげ": 1,
"ふが": 2, // 最後のkey-valueペアの後のカンマはあってもなくても良い
};
print(map["ほげ"]); // 1
print(map["もげ"]); // null
集合
JavaのSetとほぼ同じようなAPIの集合型を作れるリテラルがあります。
final s = {1, 2, 1, 2, 1, 2};
print(s); // {1, 2}
リストリテラルで使用できたCollection-If, Collection-Forなどは集合のリテラルでも使用できます。
字形(Rune)
文字列リテラル中の \uXXXX
(XXXXは4桁の16進数)、もしくは \u{XXXXXX}
({}
中は桁数は問わない16進数)で任意のUTF-32コードポイントを表すことができます。
(とA Tour of the Dart Languageには書かれている)
var clapping = '\u{1f44f}';
print(clapping); // 👏
シンボル
JavaにもKotlinにも無い概念。
プログラム中の演算だか同定だかに使用するもので、RubyのSymbolに近いもののようですが、なんか普段使うことがないので気にしなくて良いらしいです。
API作る人とかはもしかしたら使うことがあるのかも。
#hogehoge
のように #
で始めるとシンボルになるっぽいです。
関数
記法
名前付き関数の記法はJavaと一緒。
String greet(String name) {
return "Hello, $name";
}
/*
戻り値型を省略することで戻り値型を推論してくれるらしいですが、
可読性が悪いので省略しないことを勧める、ってEffective Dartに書いてあるそうです
greet(String name) {
return "Hello, $name";
}
*/
引数だとかで関数型を求められるような場所にはラムダ式を書けます。
変数にラムダ式で宣言した関数を放り込むことも可能。
var l = [1,2,3];
l.forEach((i) { print(i); });
// 中身が1行だけなら省略形も可能。
// l.forEach((i) => print(i));
ちなみにファットアロー記法 () => exp
は () { return exp; }
のシンタックスシュガーらしいです。ラムダ式以外にも、クラスのメソッドの宣言時にも使用できます。
ちゃんと値も返してくれます。
名前付き任意パラメータ(Optional named parameter)と順序付き任意パラメータ(Optional positional parameter)
Javaにはない概念です。
Kotlinのデフォルト値付き引数で良いじゃん、って気持ちになる。
Dartの関数は必須パラメータと任意パラメータの二種類をとることができます。
任意パラメータは、関数呼び出し時に値を渡されなくても大丈夫なパラメータです。
そして任意パラメータには、名前付き任意パラメータと順序付き任意パラメータの二種類があります。めんどくさい。
必須パラメータは任意パラメータより先に書かねばならず、任意パラメータは {}
(名前付き任意パラメータ)または []
(順序付き任意パラメータ)でくくる必要があります。
名前付き任意パラメータは、関数呼び出し時に引数ラベルを書かねばならない(代わりに記述順序は任意になっている)引数です。値を渡さない場合はラベルを書かなくても構いません。
値が渡されなかった仮引数には null
が入ります。
void enableFlags({bool bold, bool hidden}) {...}
// boldにはnull、hiddenにはtrueが渡される
enableFlags(hidden: true);
ちなみに、名前付き任意パラメータの仮引数に @required
アノテーションを付けると、その仮引数はラベル付きの必須パラメータになります。
@required
はmetaパッケージに含まれているため、使う際には package:meta/meta.dart
をimportする必要があります。
ややこしい。
// childに値を渡さないとエラーになる
const Scrollbar({Key key, @required Widget child})
順序付き任意パラメータは、特定の位置以降の引数を省略可能にします。
こちらも値が渡されなかった仮引数には null
が入ります。
String say(String from, String msg,
[String device, String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
// 第三引数以降は省略可
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
// 引数ラベルは書く必要がない
// 第四引数だけの省略も可能
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
デフォルト値
任意パラメータはデフォルト値を持つことができます。
基本Kotlinと同じですが、「デフォルト値をもたせた引数以後の引数にもデフォルト値設定しないといけない」的な縛りはないです。
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');
デフォルト値として与える値はコンパイル時定数であれば何でも良いです。なので、 const [...]
とか const {...}
とかも設定可能。
スコープ
JavaやKotlinと同じだと考えておけば良さそう。
演算子
Javaにはないものだけ解説。
// Kotlinと同様に ?. で「nullでなかったらメンバ呼び出し」が可能
List<dynamic> list = null;
list?.add(3); // addは呼び出されないのでエラーにならない
// Javaの / 相当の除算は ~/
int div = 4 ~/ 2; // 2
div = 4 / 2; // / だけだとdoubleを返すので、これはコンパイルエラー
// instanceof 相当の型確認演算子は is
bool isHoge = h is Hoge;
bool isNotHoge = h is! Hoge; // == !(h is Hoge)
// 実はKotlinのSmart Cast相当の事ができる
if (p is Person) {
print(p.firstName);
}
// Kotlinの ?: (エルビス演算子)相当は ?? (null合体演算子)
var b = a ?? "hoge";
// 変数がnullだったら代入する ??= 演算子がある
a ??= "hoge";
カスケード記法
A Tour of the Dart Languageではなぜか演算子の節にまとめられていたけれども、注記に「厳密にはDartの文法機能だよ」と書かれていた。
(Conditional expressionsのNote:
のところ)
インスタンスの後に ..
を続けることで、メンバにアクセスした上で、その後も ..
を続けて書くことができます。
これを使えばKotlinのapplyスコープ関数相当の事ができます。
querySelector('#confirm') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
/*
上記は以下のコードに相当する
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
*/
// ()で結合順序を明示する必要があるものの、ネストも可能
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
制御構文
基本は全部Javaと一緒。for-inとかも一緒。
でもIterable舐めるためだったら普通にforEach使ったほうが良い。
switch-case文だけちょっと違いがある。
- すべてのインスタンスをswitchできる?(比較は
==
でなされる)- 少なくともint, String, 列挙型は可能
-
==
をoverrideしているクラスは対応しないっぽい
- case節には必ず
break
もしくはcontinue
を書かないといけない- default節(必ず最後に書く必要あり)はbreak書かなくて良い
-
contine label;
で、label:
をつけたcase節に飛ぶことができる(Cのswitch-caseでbreak省略すると次のcase節の内容が実行されるのと似たような挙動再現のための機能)
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// Continues executing at the nowClosed label.
nowClosed:
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
例外処理
Javaと似てる。
try {
throw Exception("\レイガイダヨ/");
// なんと任意のインスタンスを例外として投げることが可能
// 当然型による例外型による条件分類がでめんどくさくなるのでやめたほうが良いと書かれている
// ならなぜそんな実装にしたんだ…
// throw "\レイガイダヨ/";
} on Exception catch(e) {
// 捕まえる型を指定するには on ~~ catch を使う
// eはException型
// retrhowでtry-catch-finallyブロックの外に例外を投げ直す事ができる
rethrow;
} catch (e, s) {
// 型を指定しないcatchは、何型かわからない例外全部キャッチする
// catchに仮引数を2つ指定すると、2つ目はStackTraceオブジェクトが入る
} finally {
// finallyブロックは省略可。
}
クラス
クラス宣言の記法とか、すべてのクラスはObjectの子クラスである、という点とかはJavaそっくり。
なお this
は通常省略するのがDart流らしいです。
(この辺どうするかはチームのコーディングルールによるでしょう)
細かく違うところをざっくり。
- Javaの
instance.class
相当はinstance.runtimeType
。Type
型のインスタンスが返る - インスタンス化する際の
new
はDart2から書いても書かなくても良い
コンストラクタでフィールドに値を代入する
Kotlinのコンストラクタ引数に val/var
を付けるの下位互換っぽいの。
class Person {
final String firstName;
final String lastName;
// これだけでフィールドに値を代入できる
// コンストラクタのbody内ではすでに代入された状態で使用できる
// this省略しちゃうと別の仮引数として扱われちゃうっぽい
Person(this.firstName, this.lastName) {
...
}
}
名前付きコンストラクタ
Dartのクラスはデフォルト(名前無し)コンストラクタと名前付きコンストラクタを持つことができます。
名前付きコンストラクタはファクトリメソッドの代わりに便利。
そもそもDartはメソッドオーバーロードができないので、複数コンストラクタを持つクラスを作る方法はこれしかない模様。
class Person {
final String firstName;
final String lastName;
Person(this.firstName, this.lastName);
Person.anonymous() {
this.firstName = "Anoymous";
this.lastName = "";
}
}
var p = Person.anoymous();
コンストラクタ継承
Javaと同じく、Dartのコンストラクタは自動的に継承されないので、親クラスのコンストラクタを使いたい場合は明示的に呼び出す必要があります。
でもって、それ専用の記法があります。
class Mutant extends Person {
Mutant(String firstName, String lastName) : super(firstName, lastName);
Mutant.anonymous() : super.anonymous() {
print("anonymous mutant");
}
}
親クラスがデフォルトコンストラクタ(名前無し、かつ、引数なしのコンストラクタ)を持っていない場合は、子クラスは何かしら親クラスのコンストラクタを継承しないといけない決まりがあります。
ちなみに super
ではなく this
で自クラスのコンストラクタにリダイレクトすることもできます。
初期化子リスト
親クラスコンストラクタを呼び出す記法を使って、メンバの初期化や引数の条件確認などを、コンストラクタのbodyが実行される前に行うことができます。
面白いとは思うけど、正直これに意味はあるのだろうか… final
なフィールドを初期化するときは初期化子リスト内で初期値を代入する必要があります(コンストラクタのbody内で初期化しようとするとコンパイルエラーになる)。
class Hoge {
final String fuga;
Hoge(String moge)
: assert(moge != null),
fuga = moge.toUpperCase() {
print(fuga);
}
}
/*
これは fugaなんていうセッターはない と怒られてコンパイルエラーになる
class Dame {
final String fuga;
Hoge(String moge) {
fuga = moge.toUpperCase();
}
}
*/
不変コンストラクタ
フィールドが全部 final
宣言されていて、コンストラクタの頭に const
がついていれば、その不変コンストラクタから初期化したインスタンスはコンパイル時定数になれます。
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
const p = Point(1, 2);
ファクトリコンストラクタ
factory
をコンストラクタにつけると、ファクトリコンストラクタになります。
ファクトリコンストラクタはJavaのファクトリメソッドとほぼ同じで、中で明示的にインスタンスを返さねばなりません。
シングルトンパターンをするときに便利。
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
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);
}
}
メソッド
クラス内に関数書けばいい。
this
で自インスタンスを指せる以外は何も変わらない。
なお、Javaとは違い、メソッドオーバーロードはできません。
基本的に任意パラメータがあればオーバーロードできなくても困ることはないと思いますが。
ゲッタ/セッタ
メソッドの特殊系。
Kotlinのカスタムgetter/setterの記法と考え方が似てるっちゃ似てるが、どうやら field
みたいに値をしまうバッキングフィールドまでは用意してくれないみたい。
なので、バッキングフィールドが欲しかったら自分でprivateなフィールドを宣言しておいてやる必要がある。
なんてこったい。
class Rect {
final Point point;
final Size size;
num get area => size.width * size.height;
// バッキングフィールドは自動生成されないので自分で作ってやる必要がある
Color _color;
Color get color => _color;
set color(Color newColor) {
_color = newColor;
notifyColorChanged();
}
}
抽象クラスと抽象メソッド
実装しないと怒られるメソッド宣言を持つクラスを宣言できます。
Javaと一緒。
abstract class Animal {
void hello();
}
class Cat extends Animal {
void hello() {
print("meow");
}
}
暗黙的に作られるインターフェイス
Dartには interface
キーワードが存在しませんが、クラスを宣言した時点でそのクラスと同じAPIのinterfaceが勝手に作られています。
インターフェイスは実装を持ちません。
Javaと同じく implements
キーワードで実装可能です。
// Personクラスの宣言であり、greet()メソッドを持ったPersonインターフェイスの宣言でもある。
class Person {
// privateなものはインターフェイスには含まれない
final _name;
// コンストラクタもインターフェイスには含まれない
Person(this._name);
// このメソッドのシグネチャはインターフェイスに含まれる
String greet(String who) => 'Hello, $who. I am $_name.';
}
// Personインターフェイスを実装する
class Impostor implements Person {
get _name => '';
// greetを実装しないと怒られる
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()));
}
「すべてのクラスはクラスであると当時にインターフェイスであり、extendsするかimplementsするかで性質が違う」と捉えても良いのかもしれない?
継承
Java同様の継承もできます。Javaと同じでDartは単一のクラスの継承のみ可。
キーワードとかも親クラスの呼び出し方も全部いっしょ。
メソッドオーバーライド
Javaと同様 @override
アノテーションを付けることが可能です。任意です。強制にしてくれればいいのに。
オーバーライドするメソッド引数の型を元のメソッドの型より狭めたい場合は、 covariant
キーワードを付ければ型を変えつつオーバーライドできます。
class Animal {
void chase(Animal x) { ... }
}
class Mouse extends Animal { ... }
class Cat extends Animal {
void chase(covariant Mouse x) { ... }
}
演算子オーバーライド
C++やGroovyなどと同様に演算子の上書きが可能です。
上書き可能な演算子については公式のページ参照。
ただし ==
を上書きする場合は hashCode()
メソッドも一緒に上書きする必要があります。
Javaの equals
と hashCode
同時にいじらないとよくない、というのと似てる(両方上書きしないとHashMapとかに入れたとき挙動がおかしくなるので)。
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);
// Operator == and hashCode not shown. For details, see note below.
// ···
}
noSuchMethod()
Rubyの method_missing
みたいなメソッドとして noSuchMethod
なるメソッドがあります。
宣言されていないメソッドをinvokeした際に呼び出されるメソッドです。
これを許しちゃうとタイプセーフとは言えなくなるのでは…? と思える曲者ですが、どうやら昔の名残で残っている様子。
noSuchMethodが呼び出されるには以下の条件があって、どれか一つに該当する必要があります。
- レシーバーが
dynamic
型である- こちらのパターンの場合、実行時に警告が出る
Warning: '(メソッド名)' is used reflectively but not in MirrorsUsed. This will break minified code.
- @MirrorsUsedを使うと、事前に指定したメンバの呼び出しについては警告が出なくなる。
- こちらのパターンの場合、実行時に警告が出る
- レシーバーの型のメソッドが実装を持っておらず、かつ
noSuchMethod
がオーバーライドされてObject#noSuchMethod
と実装が異なっている
class A {
@override
void noSuchMethod(Invocation invocation) => print("method ${invocation.memberName} is missing");
}
class B {
// なんとabstract classでなくても抽象メソッドは持てる
void hoge();
@override
void noSuchMethod(Invocation invocation) => print("method ${invocation.memberName} is missing");
}
void main() {
dynamic a = A();
a.hoge();
// なんとabstract classでなければ抽象メソッドを実装しないままインスタンス化できる。嘘だろおい
B b = B();
b.hoge();
}
列挙型
Javaとほぼ同じ。
列挙子は宣言された順にインデックス(0始まり)が割り振られていて、 index
で参照できる。
enum Color { red, green, blue }
assert(Color.green.index == 1);
switch-case文でも使用可能。
enumを継承できなかったり(mixinにも使えない)、enumのインスタンスを自前で生成できない(定数のみしか使えない)というのもJavaのenumと同じ。
ただし実装を持つことができないのがJavaのenumと違うところ。
ミックスイン
継承時、mixinクラスを with
キーワードでミックスイン(継承せずに実装を取り込む)できます。
継承ではないため、複数のクラスをmixinできます。
mixin AndroidEngineer {
void implementAndroidApp() {
print("got it!");
}
}
mixin IOSEngineer {
void implementIOSApp() {
print("Jobs!!");
}
}
class Person {
}
class FlutterEngineer extends Person with AndroidEngineer, IOSEngineer {
}
void main() {
final e = FlutterEngineer();
e.implementAndroidApp();
e.implementIOSApp();
}
with
は extends
キーワードとセットでないと使えないので、特に何かを継承せず機能だけ取り込みたい場合は extends Object with 〜〜
としておくと良いかも。
ミックスインに継承による拡張を施すことはできません。
が、 on
でミックスイン先を特定のクラスに制限することができるので、これを利用して既存のミックスインを拡張することが可能です。
mixin Engineer {
void say(String s) {
print("Hello world, $s");
}
}
mixin AndroidEngineer on Engineer {
void implementAndroidApp() {
print("got it!");
}
@override
void say(String s) {
print("Hello droid, $s");
}
}
class Person {
}
class FlutterEngineer extends Person with Engineer, AndroidEngineer {
}
クラス変数とクラスメソッド
Javaと全く同じです。フィールド宣言やメソッド宣言に static
を付けるだけ。
static
メソットはコンパイル時定数扱いなので、constantコンストラクタの引数に渡すことができます。
ジェネリクス
Javaと基本的に同じ。若干違うところもある。
型変数の型を保持する期間
Javaの型変数の状態はコンパイル時にのみ使われていて、実行時には保持されていません。
Dartでは実行時まで保持されているので、実行時に型変数の型を気にするようなコードも書けます。
final strList = <String>[
"hoge", "fuga", "moge"
];
assert(strList is List<String>);
型変数に制限を加える
Javaと同様に <T extends S>
で「TはSまたはSの小クラスである型」とすることができます。
が、Javaと同様に <T extends A & B & C>
のようにアンパサンドでつないで複数の条件を設けることはできないようです。
(文法エラーになる)
メソッドのジェネリクス
Javaとは文法が違うので注意。
// 型変数の定義を書く位置がメソッド名と仮引数リストの間になっている
// (Javaではアクセス修飾子の戻り値型の間だった)
T doHoge<T>(T value){ ... }
昔はメソッドでしかジェネリクス使えなかったっぽいですが、Dart 2では通常の関数でもジェネリクスできるとのこと。
ライブラリ
ライブラリを使う
import '(ライブラリURI)'
でライブラリを読み込めばOK。
ライブラリのURI
ライブラリは全部URIで表されるようになってます。
接頭辞はジャンルによって違うので、使い分けに注意。
- Dart標準ライブラリ…
dart:
以下 - サードパーティ製ライブラリ(パッケージマネージャで導入されたライブラリ)…
package:
以下- Flutterのライブラリもサードパーティにあたる
- ネイティブ実装のライブラリ…
dart-ext:
以下
相対パス指定でパッケージライブラリ化していないソースも参照できます。
ライブラリ名に別名を付ける
Kotlinのimportと同じ。
import "package:mylib/awesome_functions.dart" as awesome;
ライブラリの一部の機能だけ使う/除外する
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
ライブラリの遅延ロード
なんと遅延ロードが可能です。
使いどころとしては以下のような時があるようです。
- アプリケーションの起動を早くしたい場合
- A/Bテストなどの用途で、都合によって実装を切り替える場合
- 稀にしか出さないような画面やダイアログなど、普段は使わない機能を読み込まず軽くしておきたい場合
遅延ロードするには deferred as
で別名を付けておく必要があります。
import "package:rarely_used/lib.dart" deferred as rare;
loadLibrary()
を呼び出すと読み込みが行われますが、Future
を返すメソッドなので await
と使わないときついと思います。
Future doSomething() async {
await rare.loadLibrary();
rare.awesomeFunction();
}
注意事項とかもいくつかあります。
- 遅延ロードされるライブラリ内でconst宣言されている定数は、ロードを要求する側では定数として扱えません。
- コンパイル時にその値が存在してないんだから当然といえば当然。
- 遅延ロードするライブラリで宣言されているクラスは使用できません
- コンパイル時にそのクラスが無いんだから当然といえば当然。
-
deferred as (namespace)
のnamespaceに勝手にFuture loadLibrary()
メソッドが追加されるので注意- 同シグネチャのメソッドを置いておいたりしない限り大丈夫
ただ、どうやらFlutterのDartではまだ実装できていないらしいです。残念。
設計を考えておかないと使い所が難しい印象。
ライブラリを作る
pubspec.yaml
を置いたり一定のディレクトリ構造を守る必要があったりとか、いろいろ決まり事があります。
外に公開するライブラリを作る機会はまだなさそうなので、必要になったらドキュメントを読みましょう -> Create Library Packages
lib/
にアプリケーションコードを配置させてたりするので、FlutterアプリケーションもDart的にはライブラリなんですね。
非同期サポート
C#やJavaScriptの async/await と似たような感じで、Dartも構文で非同期処理を手続き的に書けるようになっています。
Future型とasync/await
JavaScriptのPromiseと似たようなものです。
単一の値やエラーがいつか落ちてくるので、落ちてきたときに非同期に処理を行います。
詳しいAPIはFutureのAPIドキュメントを見てください。
Futureを返すメソッド内であれば、他のFutureを返すメソッドを手続き的な記法で呼び出して結果を待つことができます。
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
await (expression)
のexpressionはFutureであるのが普通だけれども、もしFuture以外の型だったら勝手にFutureでラップしたことにしてくれるらしい。
main関数もasyncにできる。その場合もちゃんと戻り値は Future<void>
にしておかないといけない。
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
async関数の戻り値は勝手にFutureでラップされる。
async
キーワードを書くのは引数リストの後。
ラムダ式もasyncにできる。
Future<String> lookUpVersion() async => '1.0.0';
try-catch文の中でもawait可能。Futureがエラーを伝えてきた時点で例外が飛んでcatchに移行されます。
Stream型とasync/await
RxJavaやってる人ならお馴染みのStreamです。
listenしてると複数の値やエラーが落ちてくるので、落ちてきたときに非同期に処理を行います。
listenで見張っていてもよいですが、Streamの値を単純なループと同じような文法で記述する await for
文があります。
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
通常のfor文と同様に break
や return
で抜けると、Streamの購読を停止できます。
await for
も async
関数内で使う必要があります。
await for
は基本的に 終りがある Streamで使うべきで、UIイベントのストリームなどのエンドレスストリームでは使うべきではありません。
(ループの外から購読を停止するAPIが無いため)
listen()
で購読すれば StreamSubscriptionのインスタンスが手に入るので、 cancel
を叩くことでエンドレスなストリームであっても購読を停止できます。
ジェネレーター
Iterable
と Stream
の値を生成する関数のことです。
Iterable
の非同期版が Stream
だと思えばよろしい。
Itrable
のジェネレーターはこう書きます。
// body前に sync* が必要
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
// yieldで値を返しつつ関数を中断できる
while (k < n) yield k++;
}
Stream
はこう。async*
に変わるだけです。
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
中身が再帰関数になっている場合は yield*
で中断するようにします。
多分、末尾再帰最適化とかいろいろやる都合で人間がコンパイラにおしえておいてやらないといけないのでしょう。
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
呼び出し可能なクラス
Kotlinの invoke()
と同じです。
call()
を実装しているクラスのインスタンスは関数同様に ()
を後置すると call()
が呼び出されます。
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
final wf = WannabeFunction();
final out = wf("Hi","there,","gang");
}
call()を実装してると関数型としても扱えるっぽいです。
Isolate
Dartにはスレッドはありません。
すべてのコードはIsolateという単位で動いています。
各Isolateが独立したメモリヒープを持っていて、(特別なことをしなければ)あるIsolateの挙動が他のIsolateの挙動を変えてしまったりする事はありません。
並列処理をしたいときなどは新しいIsolateを立ち上げて、そこで並行処理をおこなわせたりします。
Isolate間でやりとりする場合はPortを開けてそこにデータを流します。
import "dart:isolate";
import "dart:async";
void w(sendPort) {
for(int i = 0; i< 100000; i ++) {
print("waiting: $i");
}
sendPort.send("finished!");
}
main() async {
final receivePort = ReceivePort();
final sendPort = receivePort.sendPort;
await Isolate.spawn(w, sendPort);
receivePort.listen((msg){
print("message from another Isolate: $msg");
});
}
相互にやり取りする場合はもうちょっと面倒。
Isolate.spawnUri()
を使えば main()
関数が書かれた別ファイルをIsolateとして立ち上げることも可能で、Flutterアプリもこの方法で立ち上げられてます。
Typedef
Kotlinのtypealiasを関数型に限定したようなやつ。
typedef
キーワードで関数型の別名を作ることが可能です。
typedef GestureTapDownCallback = void Function(TapDownDetails details);
なんとジェネリクスにすることも可能。
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
メタデータ
DartはJava風のアノテーションでコードにメタデータを付与する事ができます。
言語組み込みのアノテーションはannotations.dartを見るとわかります。よく使うのは @override
と @deprecated
なんじゃないですかねえ。
デフォルトコンストラクタがconstコンストラクタであるクラスはアノテーションとして使用可能なようです。
クラスやらメンバやらにつけたアノテーションは、リフレクションを使えば実行時に取得可能です。
import 'dart:mirrors';
class Todo {
final String comment;
const Todo(this.comment);
}
@Todo("いつかつくる")
class Hoge {}
main() {
final h = Hoge();
final mirror = reflectClass(Hoge);
final todoRef = mirror.metadata.first;
print(todoRef.reflectee.comment);
}
コメント
大体Javaと同じ
一行コメント
final hoge = 0; // 行末までがコメント
複数行コメント
/*
何行でも
コメント
できちゃうよ
*/
ドキュメントコメント
dartdocでHTMLのAPIドキュメント吐いてくれるっぽいです。
多分IDEの補完時とかにドキュメント出してくれるだろうと思うので積極的に書いていって問題ないと思います。
/// 単一行コメントを
/// 複数つなげても
/// ひとつのドキュメントになります。
class Hoge {}
/**
Javadoc民はこっちの方が慣れてる気がする
Kotlinと同じく [Hoge] とかするとHogeクラスへ参照を張れる
*/
class Fuga {}
拡張関数
Dart 2.6から正式に、Kotlinの拡張関数と似た雰囲気の拡張関数が作れるようになりました。
extension NumExtention<T extends num> on T {
T pow(int p) => math.pow(this, p) as T;
}
print(2.pow(5)); // 32
上記のようにジェネリクスも使えますし、extension名を _
から初めてprivateにすれば、ライブラリ内に閉じ込めておくことも可能です。
ただライブラリのトップレベルでのみ宣言可能なようなので、Kotlinでは可能だった「特定のクラスを継承している子クラス内でのみ使用可能な拡張関数」というのは作れない模様。
わからないことがあったらこれを読んでください。
https://www.dartlang.org/guides/language/language-tour