*** Dart
2 の解説:主要ライブラリ編 ***
本解説はDartサイトにあるA
Tour of the Dart
Librariesの翻訳がベースになっている。この解説はDartの組込みライブラリのなかで最も一般的に使われるライブラリとそのクラス(及び抽象クラス)たちの主たる機能の使い方が紹介されている:
dart:core
|
コアとなるクラスと抽象クラスたちで組込み型たち、コレクションたち、及びその他のコア機能が含まれている。インポート文は不要
|
dart:async
|
エイシンクと発音。FutureとStream等の非同期処理関連のクラスたち
|
dart:math
|
数値常数、関数、及び乱数発生等
|
dart:convert
|
文字コード、HTML、あるいはJSONとUTF-8の変換処理の為のエンコーダとデコーダ等
|
本ページは概説であって、dart:*ライブラリの一部しかカバーしておらず、またサード・パーティのライブラリ(いわゆるPubライブラリ)は含まれていない。またプラットホーム固有の
dart:ioと
dart:htmlは別途dart:io
tourとdart:html
tourを読んで頂きたい。
ここでは主要なライブラリに含められているクラスたちを含んでいるだけでなく、それらを使用する典型的なコードが紹介されている。従ってこの言語を実際に試しながら学習するに際し、有用なサンプル・コード集としても有用である。なおDartのサンプル・コード集はこれ以外にも:
などが参考になろう。
これ以外にも多くのサンプル集がネット上に存在しており、またより具体的な問題に対してはStackOverflowにある具体的なディスカッションなども参考になる。
注意:この解説書の中のサンプル・コードはDartPadに(main()関数のボディとして)貼り付けて読者が確認できる。 DartPadはこれまでのDartiumに変わるオンラインの開発ツールで、組み込みのライブラリしか使用できないものの他のユーザとの共有が可能など多くの特徴を持っている。DartPadは開発編の「DartPad」の章でより詳しく解説されている。
dart:core
- 数値、コレクション、文字列ほか
dart:coreライブラリ(API参照)は小規模ではあるが重要な組み込み機能のセットが用意されている。このライブラリは各Dartのプログラムに自動的にインポートされる。つまりimport文でこのライブラリをインポートする必要はない。
コンソールへの出力
トップ・レベルのprint()メソッドは単一の引数(任意のObject)をとり、そのオブジェクトの文字列値(toString()で返されたもの)をコンソールに表示する。
-
print(anObject);
print('I drink $tea.');
|
基本的な文字列とtoString()に関する更なる情報は言語編の「文字列(String)」を参照のこと。
数値
dart:coreライブラリではnum、int、及びdoubleのクラスが定義されており、これらは数値を扱う際の基本的なユーティリティを有している。
intとdoubleのparse()メソッドで文字列を整数あるいは倍精度数に変換できる:
-
assert (int.parse( '42' ) == 42 );
assert(int.parse('0x42') == 66);
assert(double.parse('0.50') == 0.5);
|
あるいは、numのparse()メソッドを使うと可能なら整数に、そうでなければ倍精度数を生成する:
-
assert (num.parse( '42' ) is int);
assert(num.parse('0x42') is int);
assert(num.parse('0.50') is double);
|
整数のベースを指定するときは、radixパラメタを付加する:
-
assert (int.parse( '42' , radix: 16 ) == 66 );
|
intまたはdoubleを文字列に変換するにはtoString()メソッドを使う。小数点の右側の桁数を指定したいときはtoStringAsFixed()を使用する。文字列の有効数字を指定したいときはtoStringAsPrecision()を使用する:
-
// 文字列に変換
assert(42.toString() == '42');
// 倍精度数を文字列に変換
assert(123.456.toString() == '123.456');
// 小数点以下の桁数を指定
assert(123.456.toStringAsFixed(2) == '123.46');
// 有効数字数を指定
assert(123.456.toStringAsPrecision(2) == '1.2e+2');
assert(double.parse('1.2e+2') == 120.0);
|
更なる情報は、int、double、及びnumのAPIドキュメントを参照されたい。また
dart:mathの章も参照されたい。
文字列と正規表現
Dartにおける文字列はUTF-16コード単位の不変並び(immutable
sequence)である。言語編の「文字列」の章では文字列に関するより詳しい情報が記されている。文字列の中をサーチしてその部分を置き換えるのに正規表現を使うことができる。
Stringクラスにはsplit(),
contains(), startsWith(), endsWith()その他のメソッドが定義されている。
文字列内を調べる
文字列内の特定の箇所を調べたり、ある文字列が特定のパタンで開始または終了しているかをチェックしたりできる。例えば:
-
// ある文字列が別の文字列を含んでいるかをチェックする
assert('Never odd or even'.contains('odd'));
// ある文字列が別の文字列で開始しているか?
assert('Never odd or even'.startsWith('Never'));
// ある文字列が別の文字列で終わっているか?
assert('Never odd or even'.endsWith('even'));
// ある文字列内の別の文字列の開始場所を見つける
assert('Never odd or even'.indexOf('odd') == 6);
|
ある文字列からデータを抽出する
ある文字列から個々の文字をString又はintで取得できる。より正確には、実際は個々のUTF-16コード単位を取得する;ト音記号文字(‘\u{1D11E}’)のような高い数のコードの文字は、一文字あたり2コード単位で構成されている。
ある文字列の部分文字列を抽出したり、部分文字列のリストとしてある文字列を分割したりできる:
-
// 部分文字列をつかむ
assert('Never odd or even'.substring(6, 9) == 'odd');
// ある文字列を文字列パタンを使って分割する
var parts = 'structured web apps'.split(' ');
assert(parts.length == 3);
assert(parts[0] == 'structured');
// インデックスを使ってUTF-16コード単位を(文字列として)取得する
assert('Never odd or even'[0] == 'N');
// 全文字のリストを取得するために空の文字列をパラメタとしてsplit()を使う
// (Stringたちとして);繰り返しに便利
for (var char in 'hello'.split('')) {
print(char);
}
/*
h
e
l
l
o
*/
// 文字列内の総てのUTF-16コード単位を取得する
var codeUnitList =
'Never odd or even'.codeUnits.toList();
assert(codeUnitList[0] == 78);
|
大文字または小文字への変換
文字列の大文字並びと小文字の並びへの変換は簡単である:
-
// 大文字への変換
assert('structured web apps'.toUpperCase() ==
'STRUCTURED WEB APPS');
// 小文字への変換
assert('STRUCTURED WEB APPS'.toLowerCase() ==
'structured web apps');
|
注意:これらのメソッドは全部の言語に対し機能する訳ではない。例えば、トルコ語のアルファベットはドットがついていない
I
は正しく変換されない。
トリミングと空の文字列
trim()を使うと前後の空白の総てのトリミングができる。ある文字列が空(長さが0)かどうかをチェックするには
isEmptyを使う。
-
// 文字列のトリミング
assert(' hello '.trim() == 'hello');
// 文字列が空かどうかを調べる
assert(''.isEmpty);
// ホワイト・スペースだけの文字列は空ではない
assert(' '.isNotEmpty);
|
文字列の部分を置き換え
Stringは不変オブジェクトであるということは、生成は出来ても変更はできないことを意味する。String
API
参照を良く見れば、メソッドたちのどれもが実際Stringの状態を変えていないことに気が付くであろう。例えば、メソッドreplaceAll()はオリジナルのStringを変えることなく新しいStringを返している。
-
var greetingTemplate = 'Hello, NAME!' ;
var greeting =
greetingTemplate.replaceAll(RegExp('NAME'), 'Bob');
// greetingTemplateは変わっていない
assert(greeting != greetingTemplate);
|
文字列の生成
プログラムである文字列を生成するためにはStringBufferが使える。StringBufferはtoString()が呼ばれるまでは新しいStringオブジェクトを生成しない。writeAll()メソッドはオプショナルな2番目のパラメタを持っており、セパレータ(この場合はスペース)が指定できる。
-
var sb = StringBuffer();
sb
..write('Use a StringBuffer for ')
..writeAll(['efficient', 'string', 'creation'], ' ')
..write('.');
var fullString = sb.toString();
assert(fullString ==
'Use a StringBuffer for efficient string creation.');
|
正規表現
RegExpクラスはJavaScriptの正規表現と同じ機能を持っている。ある文字列の効率的なサーチとパタン・マッチングには正規表現を使用する。
-
// 一けた以上の数字の為の正規表現
var numbers = RegExp(r'\d+');
var allCharacters = 'llamas live fifteen to twenty years';
var someDigits = 'llamas live 15 to 20 years';
// contains()では正規表現が使える
assert(!allCharacters.contains(numbers));
assert(someDigits.contains(numbers));
// 各マッチを別の文字列で置き換える
var exedOut = someDigits.replaceAll(numbers, 'XX');
assert(exedOut == 'llamas live XX to XX years');
|
RegExpクラスで直接作業することも可能である。
Matchクラスが正規表現のマッチへのアクセスを可能にしている。
-
var numbers = RegExp( r'\d+' );
var someDigits = 'llamas live 15 to 20 years';
// この正規表現がある文字列内でのマッチを持っているかどうかをチェック
assert(numbers.hasMatch(someDigits));
// 全部のマッチをループで探す
for (var match in numbers.allMatches(someDigits)) {
print(match.group(0)); // 15, then 20
}
|
コレクション
(Collections)
Dartにはコアのコレクション(collections)の為のAPIがあり、これにはリスト(List)、セット(Set)、及びマップ(Map)のクラスが含まれている。
List
言語編で示したようにリストの生成と初期化にはリテラルが使える。他にはListのコンストラクタたちのひとつを使用する手段がある。Listクラスはまたリストにアイテムを追加したり除去したりするための幾つかのメソッドを定義している。
-
// List コンストラクタを使う
var vegetables = List();
// あるいは単純にリスト・リテラルを使う
var fruits = ['apples', 'oranges'];
// リストへの追加
fruits.add('kiwis');
// リストへの複数のアイテムの追加
fruits.addAll(['grapes', 'bananas']);
// リストの長さを取得
assert(fruits.length == 5);
// 単一のアイテムを削除
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);
// リストから総ての要素を削除
fruits.clear();
assert(fruits.length == 0);
|
あるリスト内のオブジェクトのインデックスを見つけるにはindexOf()を使う:
-
var fruits = [ 'apples' , 'oranges' ];
// インデックスを使ってリストのアイテムにアクセス
assert(fruits[0] == 'apples');
// リスト内のアイテムを見つける
assert(fruits.indexOf('apples') == 0);
|
sort()メソッドを使ってリストをソートする。二つのオブジェクトを比較するためのソート関数を指定できる。このソート関数はより少の時は<
0を、同じの時は0を、そしてより大の時は>
0を返さなければならない。以下の例ではcompareTo()メソッドを使っているが、これは
Comparableで定義されStringで実装されているものである。
-
var fruits = [ 'bananas' , 'apples' , 'oranges' ];
// リストをソートする
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');
|
リストは型がパラメタ化されているので、そのリストが含むべき型を指定できる:
-
// このリストは String のみ含んでいなければならない
var fruits = List<String>();
fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);
|
-
fruits.add( 5 ); // Error: 'int' は 'String' に代入できない
|
リストの総てのメソッドはAPI参照を見て頂きたい。
Set
Dartに於けるセットはユニーク(各々が同じ物ではない)なアイテムたちの順序なしのコレクションである。セットには順序がないので、セット内のアイテムをインデックス(場所)で得ることはできない。
-
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);
// 重複したアイテムを加えても効果を及ばさない
ingredients.add('gold');
assert(ingredients.length == 3);
// あるセットからあるアイテムを除去する
ingredients.remove('gold');
assert(ingredients.length == 2);
|
あるセット内にひとつまたは複数のオブジェクトが存在するかをチェックするにはcontains()とcontainsAll()を使う:
-
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
// あるアイテムがそのセットに存在するかをチェックする
assert(ingredients.contains('titanium'));
// そのセットに総てのアイテムたちが存在するかをチェックする
assert(ingredients.containsAll(['titanium', 'xenon']));
|
intersectionは自分のセット内のアイテムたちがふたつの他のセット内にも存在しているようなセットである。
-
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
// 二つのセットのインターセクションを作る
var nobleGases = Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));
|
セットの総てのメソッドはAPI参照を見て頂きたい。
Map
マップ(map)は一般的にディレクトリ(dictionary)またはハッシュ(hash)として知られているものだが、これはキーと値のペアの順序付けなしのコレクションである。マップはキーとある値を結びつけて検索し易くしている。JavaScriptと違って、Dartのオブジェクトはマップではない。
簡潔なリテラルの構文を使ってマップを宣言できるし、従来のコンストラクタでも宣言できる:
-
// Map ではキーとして文字列を使うことが多い
var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai': ['Hanalei', 'Poipu']
};
// Mapはコンストラクタから作ることができる
var searchTerms = Map();
// Mapはパラメタ化された型たちである:キーと値にどの型が
// 使われるべきかを指定できる
var nobleGases = Map<int, String>();
|
マップのアイテムの追加、取得、及びセットには角括弧構文を使う。あるマップからキーとその値を削除するにはremove()を使う。
-
var nobleGases = { 54 : 'xenon' };
// キーを使って値を取り出す
assert(nobleGases[54] == 'xenon');
// マップがそのキーを含んでいるかどうかをチェックする
assert(nobleGases.containsKey(54));
// キーとその値を削除する
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));
|
あるマップから総ての値たち、または総てのキーたちを取り出すことが可能である:
-
var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai': ['Hanalei', 'Poipu']
};
// 全部のキーを順序なしのコレクションとして取得する
// (an Iterable)
var keys = hawaiianBeaches.keys;
assert(keys.length == 3);
assert(Set.from(keys).contains('Oahu'));
// 順序なしのコレクションとして総ての値たちを取得する
// (an Iterable of Lists).
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));
|
そのマップがあるキーを含んでいるかをチェックするにはcontainsKey()を使う。マップの値たちがnullということもあり得るので、単にそのキーに対する値を取得し、そのキーの存在性を判断するのにnullチェックをすることには依存できない。
-
var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai': ['Hanalei', 'Poipu']
};
assert(hawaiianBeaches.containsKey('Oahu'));
assert(!hawaiianBeaches.containsKey('Florida'));
|
そのキーがマップ内に既に存在していない場合に限りそのキーにある値を割り当てるにはputIfAbsent()メソッドを使う。その値を返す関数を用意してやる必要がある。
-
var teamAssignments = {};
teamAssignments.putIfAbsent(
'Catcher', () => pickToughestKid());
assert(teamAssignments['Catcher'] != null);
|
リストの総てのメソッドはAPI参照を見て頂きたい。
コレクションに共通なメソッドたち
List,
Set,及びMapには多くのコレクションに存在する共通の機能を有している。この共通の機能の一部はIterableクラスで定義されておりそれはListとSetが実装している。
注意:MapはIterableを実装してはいないが、そのマップのkeysとvaluesのプロパティを使ってそこからIterableのオブジェクトを取得できる。
List,
Set,及またはMapがアイテムを有しているかどうかをチェックするには
isEmptyまたは
isNotEmptyを使用する:
-
var coffees = [];
var teas = ['green', 'black', 'chamomile', 'earl grey'];
assert(coffees.isEmpty);
assert(teas.isNotEmpty);
|
List,
Set,及またはMapのなかの各アイテムにある関数を適用するにはforEach()が使える:
-
var teas = [ 'green' , 'black' , 'chamomile' , 'earl grey' ];
teas.forEach((tea) => print('I drink $tea'));
|
Map上でforEach()を呼ぶときは、その関数は2つの引数(キーと値)を取らねばならない:
-
hawaiianBeaches.forEach((k, v) {
print('I want to visit $k and swim at $v');
// I want to visit Oahu and swim at
// [Waikiki, Kailua, Waimanalo], etc.
});
|
Iterableたちはmap()メソッドを用意しており、総ての結果を単一のオブジェクトに入れてくれる:
-
var teas = [ 'green' , 'black' , 'chamomile' , 'earl grey' ];
var loudTeas = teas.map((tea) => tea.toUpperCase());
loudTeas.forEach(print);
/*
GREEN
BLACK
CHAMOMILE
EARL GREY
*/
|
注意:map()で返されるオブジェクトは
Iterableであって、それは後回しで(lazily)計算される:つまりその関数は返されたオブジェクトからあるアイテムを取り出すまでは呼ばれない。
自分の関数が各アイテムに対し即座に呼ばれるようんしたいときは、map().toList()またはmap().toSet()を使用する:
-
var loudTeas =
teas.map((tea) => tea.toUpperCase()).toList();
|
ある条件にマッチする総てのアイテムたちを得るにはIterableのwhere()メソッドを使う。一部またはすべてのアイテムがある条件を満たすかどうかをチェックするにはIterableのany()とevery()メソッドを使用する。
-
var teas = [ 'green' , 'black' , 'chamomile' , 'earl grey' ];
// Chamomileはdecaffeinatedではない
bool isDecaffeinated(String teaName) =>
teaName == 'chamomile';
// 用意した関数からtrueを返す特定のアイテムたちだけを
// 探すには、 where()を使う
var decaffeinatedTeas =
teas.where((tea) => isDecaffeinated(tea));
// or teas.where(isDecaffeinated)
// そのコレクションの中の少なくともひとつのアイテムが
// ある条件を満たすかどうかをチェックするにはany()を使う
assert(teas.any(isDecaffeinated));
// そのコレクションの中の総てののアイテムが
// ある条件を満たすかどうかをチェックするにはevery()を使う
assert(!teas.every(isDecaffeinated));
|
総てのメソッドのリストに関しては、
Iterable
API参照と、
List,
Set及びMapのAPI参照を見られたい。
URI
UriクラスはURI(URLとしても知られている)で使われる文字列をエンコードとデコードする関数を用意している。これらの関数は&と=といったURIにとって特別な文字に対処している。UriクラスはまたあるURIのホスト、ポート、スキーム等々の要素を解析及び露出させる。
完全修飾のURI(fully
qualified URIs)のエンコードとデコード
URIにとって特別の意味を持つ文字(例えば
/,
:, &,
#)を除く文字をエンコードとデコードするには、encodeFull()とdecodeFull()メソッドを使用する。これらのメソッドは完全修飾のURIのエンコードとデコードには有用で、特別なURI文字には手を付けない。
-
var uri = 'http://example.org/api?foo=some message' ;
var encoded = Uri.encodeFull(uri);
assert(encoded ==
'http://example.org/api?foo=some%20message');
var decoded = Uri.decodeFull(encoded);
assert(uri == decoded);
|
someとmessageの間のスペース文字がどのようにエンコードされるかに着目されたい。例えば
/
は%2Fとエンコードされる。
URIを解析する
URIのオブジェクトまたはURI文字列を扱っているときは、pathといったUriのフィールドを使ってその要素を取り出すことができる。文字列からUriを生成するには、クラス(static)メソッドのparse()を使用する:
-
var uri =
Uri.parse('http://example.org:8080/foo/bar#frag');
assert(uri.scheme == 'http');
assert(uri.host == 'example.org');
assert(uri.path == '/foo/bar');
assert(uri.fragment == 'frag');
assert(uri.origin == 'http://example.org:8080');
|
利用できるURI要素は
Uri
API参照に記されている。
URIの構築
Uri()コンストラクタを使って個々の要素からあるURIを構築できる:
-
var uri = Uri(
scheme: 'http',
host: 'example.org',
path: '/foo/bar',
fragment: 'frag');
assert(
uri.toString() == 'http://example.org/foo/bar#frag');
|
日時処理
(Dates and times)
DateTimeオブジェクトは現時点の時刻である。タイムゾーンはUTCか地域のローカル・タイムゾーンのいずれかである。
DateTimeのオブジェクトは幾つかのコンストラクタを使って生成できる:
-
// 現在の日時を取得
var now = DateTime.now();
// ローカル・タイムゾーンで新しいDateTimeを生成
var y2k = DateTime(2000); // January 1, 2000
// 月日を指定する
y2k = DateTime(2000, 1, 2); // January 2, 2000
// UTCでの日付を指定
y2k = DateTime.utc(2000); // 1/1/2000, UTC
// Unixエポックからのmsで日時を指定
y2k = DateTime.fromMillisecondsSinceEpoch(946684800000,
isUtc: true);
// ISO 8601の時刻を解析
y2k = DateTime.parse('2000-01-01T00:00:00Z');
|
DateTimeオブジェクトのmillisecondsSinceEpochプロパティは“Unix
epoch”即ちJanuary
1, 1970, UTCからのミリ秒数を返す:
-
// 1/1/2000, UTC
var y2k = DateTime.utc(2000);
assert(y2k.millisecondsSinceEpoch == 946684800000);
// 1/1/1970, UTC
var unixEpoch = DateTime.utc(1970);
assert(unixEpoch.millisecondsSinceEpoch == 0);
|
ふたつのDateTimeオブジェクト間の差を計算したりあるDateTimeオブジェクトの時刻を前後にシフトさせたりするにはDurationクラスを使用する:
-
var y2k = DateTime.utc( 2000 );
// 1年間を加算
var y2001 = y2k.add(Duration(days: 366));
assert(y2001.year == 2001);
// 30日分を引く
var december2000 = y2001.subtract(Duration(days: 30));
assert(december2000.year == 2000);
assert(december2000.month == 12);
// 2つの日時の差を計算しDurationのオブジェクトを返す
var duration = y2001.difference(y2k);
assert(duration.inDays == 366); // y2kはうるう年だった
|
警告:時刻のシフト(例えば夏時間)があるので、日にちでDateTimeをシフトさせるのは問題を起こし易い。どうしてもシフトさせたいのならUTCを使用すること。
全メソッドはDateTimeとDurationのAPI参照に記載されている。
ユーティリティ・クラス
coreライブラリにはいろんなユーティリティ・クラスがあり、ソーティング、値のマッピング、及び繰り返し処理などに有用である。
オブジェクトの比較
Comparableインターフェイスを実装することは、通常ソーティングの為にそのオブジェクトが他のオブジェクトと比較できるようになることを示す。compareTo()メソッドはより少の時は<
0を、等しいときは0を、そしてより大の時は
> 0を返す。
-
class Line implements Comparable<Line> {
final int length;
const Line(this.length);
@override
int compareTo(Line other) => length - other.length;
}
void main() {
var short = const Line(1);
var long = const Line(100);
assert(short.compareTo(long) < 0);
}
|
マップのキーを実装
Dartでは各オブジェクトには整数のハッシュ・コードが割り当てられており、従ってそれをマップのキーとして使うことが可能である。しかしながら、
hashCodeゲッタをオーバライドしてカスタムのハッシュ・コードを発生させることも可能である。そうした場合は、==演算子もオーバライドすることになろう。等しい(==によって)オブジェクトは同一のハッシュ・コードを持っていなければならない。ハッシュ・コードは独自なものでなければならないわけではないが、よく使われているものであるべきである。
-
class Person {
final String firstName, lastName;
Person(this.firstName, this.lastName);
// Effective Java, Chapter 11の手法を使って
// hashCodeをオーバライドする
@override
int get hashCode {
int result = 17;
result = 37 * result + firstName.hashCode;
result = 37 * result + lastName.hashCode;
return result;
}
// hashCodeをオーバライドするときは一般的に
// 演算子 == を実装しなければならない
@override
bool operator ==(dynamic other) {
if (other is! Person) return false;
Person person = other;
return (person.firstName == firstName &&
person.lastName == lastName);
}
}
void main() {
var p1 = Person('Bob', 'Smith');
var p2 = Person('Bob', 'Smith');
var p3 = 'not a person';
assert(p1.hashCode == p2.hashCode);
assert(p1 == p2);
assert(p1 != p3);
}
|
繰り返し
(Iteration)
IterableとIteratorクラスはfor-inに対応している。for-inループを使うためのIteratorが使えるクラスを生成するときは、Iterableを拡張(もし可能なら)または実装する。実際の繰り返し機能を定義するときはIterableを実装する。
-
class Process {
// processを表現する...
}
class ProcessIterator implements Iterator<Process> {
@override
Process get current => ...
@override
bool moveNext() => ...
}
// 全processに亘って繰り返しを可能とする不思議なクラス
// [Iterable]のサブクラスを拡張
class Processes extends IterableBase<Process> {
@override
final Iterator<Process> iterator = ProcessIterator();
}
void main() {
// Iterable objects can be used with for-in.
for (var process in Processes()) {
// そのprocessで何らかの処理をする
}
}
|
例外
Dartのコア・ライブラリには多くの例外とエラーが定義されている。例外は捕捉を事前に予測できる状況と考えられる。エラーは予測できない状況である。
最も一般的なエラーとしては次の2つがある:
受け取ったオブジェクト(nullの場合もある)がメソッドを実装していないとき
予期せぬ引数に遭遇したメソッドから投げられる
アプリケーション固有の例外を投げることはエラーが生じたということを示す一般的なやり方である。Exceptionインターフェイスを実装することでカスタムの例外を定義できる:
-
class FooException implements Exception {
final String msg;
const FooException([this.msg]);
@override
String toString() => msg ?? 'FooException';
}
|
更なる詳細は言語編の「例外
(Exceptions)」とException
API参照に記されている。
dart:async
– 非同期プログラミング
非同期プログラミングではしばしばコールバック関数が使われているが、Dartではその代替物のFutureとStreamのオブジェクトを備えている。Futureは結果が将来いつかの時点で得られるという約束(promise)のようなものである。Streamはイベントのような値のシーケンスを得る手段である。Future、Stream及びその他のクラスがdart:asyncライブラリ(API参照)に収容されている。
注意:常にFutureとStreamのAPIを直接使う必要はない。Dartの言語は
asyncとawaitのようなキーワードを使っての非同期コーディングに対応している。言語編の「非同期対応」の章に詳細が記されている。
dart:asyncライブラリはウェブ・アプリとコマンド行アプリの双方で動作する。これを使うには
dart:asyncをインポートする:
-
バージョンに関する注意:
Dart
2.1の時点ではFutureとStreamのAPIを使用するのにdart:asyncをインポートする必要はない。dart:coreがこれらのクラスをエクスポートしている。
Future
FutureオブジェクトはDartのライブラリにわたって出現している。これは非同期メソッドによってこのオブジェクトが返されているからである。Futureが完了した時点で、その値が使える状態になる。
awaitを使う
Future
APIを直接使う前に、代わりにawaitを使うことを検討されたい。await式を使うコードはFuture
APIを使ったコードよりは解りやすいものとなり得る。
以下の関数を考えてみよう。これは3つの非同期関数を順番に実行する(ひとつずつ前の関数が終了するのを待つ)のにFutureのthen()メソッドをつかっている。
-
runUsingFuture() {
// ...
findEntryPoint().then((entryPoint) {
return runExecutable(entryPoint, args);
}).then(flushThenExit);
}
|
これと等価なawait式を使ったコードはより同期コードに似たものとなる:
-
runUsingAsyncAwait() async {
// ...
var entryPoint = await findEntryPoint();
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
}
|
非同期関数はFutureからの例外を捕捉できる。例えば:
-
var entryPoint = await findEntryPoint();
try {
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
} catch (e) {
// エラーをここで処理...
}
|
重要:async関数はFutureを返す。自分の関数が将来Futureを返したくないのなら、別のソリューションを使う。例えば、自分の関数から非同期関数を呼んでも良い。
awaitの使用と関連したDart言語の機能に関しては言語編の「非同期対応」の章を読んで頂きたい。
基本的な使用法
futureが完了したときに走るコードのスケジューリングにthen()が使える。例えば、HttpRequest.getString()はHTTP要求はしばらく時間がかかり得るのでFutureを返す。そのFutureが完了し、約束された文字列が取得できるようになったときに何らかのコードを走らせるのにthen()を使用する。
-
HttpRequest.getString(url).then((String result) {
print(result);
});
|
Futureオブジェクトが投げる可能性があるエラーまたは例外を扱うにはcatchError()を使用する:
-
HttpRequest.getString(url).then((String result) {
print(result);
}).catchError((e) {
// そのエラーを処理または無視
});
|
then().catchError()のパタンはtry-catchの非同期版である。
重要:then()の結果(オリジナルのFutureの結果に対してではなく)に対しcatchError()を呼び出すことを間違えないこと。でないと、catchError()はオリジナルのFutureの完了からのエラーのみを扱ってしい、thenで登録されたハンドラからのエラーは扱われなくなる。
複数の非同期メソッドたちのチェイン
then()メソッドはFutureを返し、複数の非同期関数たちをある順番で走らせるための有用な手段となっている。もしthen()に登録されたコールバックがFutureを返すなら、then()は等価なFutureを返す。もしこのコールバックが他の型の値を返すならthen()はその値で完了する新しいFutureを生成する。
-
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* 例外処理... */
});
|
この例では、メソッドたちは次の順番で走る:
costlyQuery()
expensiveWork()
lengthyComputation()
以下はawaitを使った同じコードである:
-
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* 例外処理... */
}
|
複数のfutureを待つ
自分のアルゴリズムが多くの非同期関数を呼び出し、そのすべてが完了したときに次に進む必要がある場合もあろう。複数のFutureたちをこなし、それらが完了するのを待つにはstaticメソッドのFuture.wait()を使用する:
-
Future deleteLotsOfFiles() async => ...
Future copyLotsOfFiles() async => ...
Future checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
|
Stream
StreamオブジェクトはDartのライブラリにわたって出現している。これはデータのシーケンスを表現している。例えば、ボタン・クリックのようなHTMLイベントたちはStreamを介して渡される。ファイルもまたStreamとして読みだされる。
非同期のforループを使う
Stream APIを使う代わりに非同期forループ(await
for)が使える。
次の関数を考えてみよう。これはStreamのlisten()メソッドを使ってファイルのリストに加入(subscribe)し、各ファイルまたはディレクトリをサーチする関数リテラルにそれを渡している。
-
void main(List<String> arguments) {
// ...
FileSystemEntity.isDirectory(searchPath).then((isDir) {
if (isDir) {
final startingDir = Directory(searchPath);
startingDir
.list(
recursive: argResults[recursive],
followLinks: argResults[followLinks])
.listen((entity) {
if (entity is File) {
searchFile(entity, searchTerms);
}
});
} else {
searchFile(File(searchPath), searchTerms);
}
});
}
|
これと等価なawait式を使ったコード(非同期forループ(await
for)を含む)は、より同期コードに似たものになる:
-
Future main(List<String> arguments) async {
// ...
if (await FileSystemEntity.isDirectory(searchPath)) {
final startingDir = Directory(searchPath);
await for (var entity in startingDir.list(
recursive: argResults[recursive],
followLinks: argResults[followLinks])) {
if (entity is File) {
searchFile(entity, searchTerms);
}
}
} else {
searchFile(File(searchPath), searchTerms);
}
}
|
重要:await
forを使うに際しては、それがより明確なコードになり、そのストリームの結果たちの総てを待つ必要があるようになっていることを確認する。例えば、DOMはイベントのエンドレスなストリームを送信するので、通常DOMイベント・リスナにawait
forを使うべきではない。二つのDOMイベント・リスナたちを順に登録するためにawait
forを使うと、二つ目のイベントは扱われなくなってしまう。
awaitと関連したDart言語の機能に関しては言語編の「非同期対応」の章を読んで頂きたい。
ストリーム・データのリスン
各値が到着するごとに取得するには、await
forを使うか又はlisten()メソッドを使ってそのストリームに加入するかのどちらかを使う:
-
// ID でボタンを見つけ、イベント・ハンドラを付加する
querySelector('#submitInfo').onClick.listen((e) {
// このボタンがクリックされたら、このコードが走る
submitData();
});
|
この例では、onClickプロパティは“submitInfo”ボタンによってもたらされるStreamのオブジェクトである。
もし単一のイベントのみを扱いたいときは、
first,
lastまたはsingleのようなプロパティを使ってそれを取得できる。それを処理する前にそのイベントをテストしたいときには、firstWhere(),
lastWhere()或いはsingleWhere()といったメソッドを使用する。
もしイベントたちのサブセットを扱いたいときは、skip(),
skipWhile(),
take(),
takeWhile()及びwhere()といったプロパティが使える。
ストリーム・データの変換
あるストリームのデータのフォーマットを変換してそれを使えるようにする場合がある。別のデータの形式でストリームを作り出すのにtransform()メソッドを使用する:
-
var lines = inputStream
.transform(utf8.decoder)
.transform(LineSplitter());
|
この例では2つのトランスフォーマ(変換器)を使用している。最初は整数からなるストリームを文字列のストリームに変換するのに
utf8.decoderを使用している。次に文字列のストリームから各行ごとのストリームに変換するのに
LineSplitterを使っている。これらの変換器は
dart:convertライブラリから持ってきたものである(dart:convertの章を参照のこと)。
エラーと完了の処理
エラーと完了の処理コードをどのように指定するかは、非同期forループ(await
for)を使うかまたはStreamのAPIを使うかに依る。
もし非同期forループを使うのなら、エラーを扱うのに
try-catchを使うことになる。該ストリームが閉じた後で実行するコードは非同期forループのあとに置かれる。
-
Future readFileAwaitFor() async {
var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();
var lines = inputStream
.transform(utf8.decoder)
.transform(LineSplitter());
try {
await for (var line in lines) {
print('Got ${line.length} characters from stream');
}
print('file is now closed');
} catch (e) {
print(e);
}
}
|
もし Stream
APIを使うのなら、
onErrorリスナを登録することでエラーを処理する。onDoneリスナを登録することで、該ストリームが閉じた後のコードを走らせる。
-
Stream<List<int>> inputStream = config.openRead();
inputStream
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((String line) {
print('Got ${line.length} characters from stream');
}, onDone: () {
print('file is now closed');
}, onError: (e) {
print(e);
});
|
ファイル操作とデーターベース・アクセス
非同期処理が重要になるのは、ヒューマン・インターフェイスに加え、ファイル操作とデータベース・アクセスである。
ファイル操作
ファイル操作は通常ディスク・ドライブをアクセスするので時間がかかる。例えば:
-
code_samples/read_file_1.dart
|
import 'dart:io' ;
main() {
print('ファイル読み出し開始');
var file = new File('code_samples/test_data.txt');
var contents = file.readAsStringSync(); // プログラムは総ての内容が読み出されるまでブロックされてしまう
print(contents);
print('読み出し完了');
}
|
dart:ioライブラリのFileという抽象クラスのreadAsStringSync([Encoding
encoding =
Encoding.UTF_8])というメソッドは、そのファイルのデータをUTF_8の文字列だとして同期して読みだし、Stringを返すメソッドである。従ってプログラムは下位層がそのファイルを読みだすまでそこで待たされてしまう(英語ではブロックするという)。
これに対しreadAsString([Encoding
encoding =
Encoding.UTF_8])というメソッドは非同期で読み出すもので、Future<String>オブジェクトを即座に返す。そのFutureオブジェクトが完了したときにプログラムが待機状態にあればthen(またはwhenComplete)で登録されたコールバック関数を実行する。
-
code_samples/read_file_2.dart
|
import 'dart:io' ;
main() {
print('ファイル読み出し開始');
var file = new File('major_libraries/test_data.txt');
file.readAsString().then((String contents) {
print(contents); // ファイルが読みだされたときに出力
print('読み出し完了');
});
// その間他の処理やイベントにも対応できる
}
|
このコードは非同期関数を使うとより簡単にあたかも同期処理のごとく記述できる:
-
code_samples/read_file_3.dart
|
import 'dart:io' ;
main() async {
print('ファイル読み出し開始');
var file = new File('code_samples/test_data.txt');
var contents = await file.readAsString(); // この間他のイベントに対応できる
print(contents);
print('読み出し完了');
}
|
ちなみにこのサンプルのテキスト・ファイルは以下のようなものである:
-
code_samples/test_data.txt
|
Dart helps you craft beautiful, high-quality experiences across all screens, with:
A client-optimized language
Rich, powerful frameworks
Delightful, flexible tooling
|
なおこれらのサンプルはDartPadでは動作しないので、「開発編」にあるIDEを使用のこと。
データベース・アクセス
サーバ側で非同期動作が必要な典型的なものがデータベースとウェブ・サービスへのアクセスであろう。ともにクライアントからの要求処理の中で最も時間がかかるものである。
以下はその典型的な使い方である:
-
import 'dart:async' ;
main() async {
var s = ConnectionSettings(
user: "root",
password: "dart_jaguar",
host: "localhost",
port: 3306,
db: "example",
);
// DB接続の確立
print("Opening connection ...");
var conn = await MySqlConnection.connect(s);
print("Opened connection!");
// DBアクセス
await dropTables(conn);
await createTables(conn);
await insertRows(conn);
await readData(conn);
// 終了したらそのDB接続を解放
await conn.close();
}
|
これはskljockeyというパッケージを使用した例である。
データベース・アクセスに関しては「開発編」にある「Pubパッケージを活用する」の章で更に詳しく解説してある。
更なる情報
コマンド行アプリケーションでFutureとStreamを使った幾つかの例が
dart:io
tourに記されている。また以下の記事とチュートリアルも参考になる:
dart:math –
数学関数と乱数
dart:mathライブラリ(API参照)では、sinとcos、最大と最小、及びpiとeといった一般的な機能が含まれている。mathライブラリの殆どの機能はトップ・レベルの関数として実装されている。
このライブラリを使うにはdart:mathをインポートする:
-
三角関数
Mathライブラリには基本的な三角関数が用意されている:
-
// Cosine
assert(cos(pi) == -1.0);
// Sine
var degrees = 30;
var radians = degrees * (pi / 180);
// radiansはこの時点で0.52359.
var sinOf30degrees = sin(radians);
// sin 30° = 0.5
assert((sinOf30degrees - 0.5).abs() < 0.01);
|
注意:これらの関数はラジアンを使っていて、度ではない!
最大と最小
Mathライブラリにはmax()とmin()のメソッドがある:
-
assert (max( 1 , 1000 ) == 1000 );
assert(min(1, -1000) == -1000);
|
Math常数
Mathライブラリにはpi、e、その他の常数がある:
-
// Math ライブラリにはその他の常数が含まれている
print(e); // 2.718281828459045
print(pi); // 3.141592653589793
print(sqrt2); // 1.4142135623730951
|
乱数
乱数はRandomクラスから得られる。オプショナルとしてRandomコンストラクタに種を用意してやることができる。
-
var random = Random();
random.nextDouble(); // 0.0と1.0の範囲: [0, 1)
random.nextInt(10); // 0と9の範囲
|
ブール値の乱数を生成することも可能である。
-
var random = Random();
random.nextBool(); // true or false
|
総てのメソッドのリストはMath
API参照にある。num、int、及びdoubleに関するAPI参照も参考されたい。
dart:convert –
JSONとUTF-8のエンコードとデコード他
dart:convertライブラリ(API参照)にはJSONとUTF-8のコンバータが収容されており、またその他のコンバータの生成支援が入っている。JSONは構造化されたオブジェクトとコレクションを表現する為のシンプルなテキスト・フォーマットである。UTF-8は一般的な可変幅のエンコーディングで、Unicode内の各文字を表現できる。
注意:日本では歴史的にSJIS(Shift-JIS、Windows-31Jなど)やEUC-JPなどのエンコーディングがまだ多用されているが、残念ながらDartではそのようなエンコーディングに対応していない。従って何らかのツールを使ってファイル・エンコーディングをUTF-8に変換しなければならない。テキストならメモ帳でエンコーディングを指定してファイルに記録できる。OpenOfficeではHTML形式の文書はツール→オプション→読み込みと保存→HTML互換性→文字集合でUTF-8を指定してやれば良いし、テキストの場合はファイル→名前を付けて保存→エンコードされたテキストを選択すればよい。その他の文書作成アプリにもこの種の機能があるし、サード・パーティのファイル変換ツールも存在する。
dart:convertライブラリはウェブ・アプリとコマンド行アプリの双方で利用できる。これを使うにはdart:convertをインポートする:
-
JSONのエンコードとデコード
JSONエンコードされた文字列をjsonDecode()を使ってDartのオブジェクトとしてデコードする:
-
// 注意: JSON 文字列内ではシングル・クオート (') ではなくて
// ダブル・クオート (")を使うこと
// これはDartではなくてJSONである
var jsonString = '''
[
{"score": 40},
{"score": 80}
]
''';
var scores = jsonDecode(jsonString);
assert(scores is List);
var firstScore = scores[0];
assert(firstScore is Map);
assert(firstScore['score'] == 40);
|
JSONに対応しているDartのオブジェクトをjsonEncode()を使ってJSONフォーマットの文字列にエンコードする:
-
var scores = [
{'score': 40},
{'score': 80},
{'score': 100, 'overtime': true, 'special_guest': null}
];
var jsonText = jsonEncode(scores);
assert(jsonText ==
'[{"score":40},{"score":80},'
'{"score":100,"overtime":true,'
'"special_guest":null}]');
|
int,
double, String, bool, null,
List,またはMap(文字列のキーつき)のみがJSONに直接エンコードできる。ListとMapのオブジェクトは再帰的にエンコードされる。
直接エンコードできないオブジェクトをエンコードするには2つの選択肢がある。最初は第2の引数、即ち直接エンコード可能なオブジェクトを返す関数つきでencode()を呼ぶものである。第2の選択肢は第2の引数なしで呼ぶもので、この場合はエンコーダはそのオブジェクトのtoJson()メソッドを呼び出す。
さらなるサンプルとJSON関連のパッケージへのリンクはJSON
Supportに記されている。
また言語編の「組込み型」の「MapとJSON」も参考のこと。
UTF-8文字のエンコードとデコード
UTF-8でエンコードされたバイト列をDartの文字列に変換するにはutf8.decode()を使用する:
-
List<int> utf8Bytes = [
0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9,
0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3,
0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4,
0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5,
0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1
];
var funnyWord = utf8.decode(utf8Bytes);
assert(funnyWord == 'Îñţérñåţîöñåļîžåţîờñ');
|
UTF-8文字のストリームをDartの文字列に変換するには、Streamのtransform()メソッドにutf8.decoderを指定してやる:
-
var lines = inputStream
.transform(utf8.decoder)
.transform(LineSplitter());
try {
await for (var line in lines) {
print('Got ${line.length} characters from stream');
}
print('file is now closed');
} catch (e) {
print(e);
}
|
Dartの文字列をUTF-8エンコードされたバイトのリストにエンコードするにはutf8.encode()メソッドを使用する:
-
List<int> encoded = utf8.encode( 'Îñţérñåţîöñåļîžåţîờñ' );
assert(encoded.length == utf8Bytes.length);
for (int i = 0; i < encoded.length; i++) {
assert(encoded[i] == utf8Bytes[i]);
}
|
その他の機能
dart:convertにはASCIIとISO-8859-1
(Latin1)の為のコンバータが存在する。詳細はdart:convertライブラリに関するAPI参照に記されている。
まとめ
本ページではDartの組込みライブラリたちのなかで最も一般的に機能を紹介した。しかしながらここでは組み込みライブラリの総てをカバーしてはいない。読者に関心がありそうなその他のライブラリとしては
dart:collectionと
dart:typed_data、及びDartウェブ開発ライブラリとFlutterライブラリのプラットホーム固有のライブラリが存在する。
pub
toolを使えばより多くのライブラリを得ることができる。collection,
crypto,
http,
intl及びtestのライブラリはpubを使ってインストールできるものの一部でしかない。
Dartの言語に関しては「言語編」を見られたい。