LoginSignup
20
19

More than 3 years have passed since last update.

Dart言語 翻訳 A tour of the Dart language +α

Last updated at Posted at 2021-02-07

はじめに

Flutterを初めた頃ですが、参考にしているソースコードやライブラリで見知らぬ書き方がありました。..が使われていたのですが、Googleで調べようにも..の名前がわからず、記号だけですとなかなか調べられないんですよね。(カスケード表記という名前だということがわかりました)
で、先日お知恵をいただいたのが A tour of the Dart language でページ内検索する。という方法でした。がしかし、英語が得意とは言えず、なかなか億劫な気持ち・・・。翻訳サイトとか使っても、それぞれの単語の意味や背景を知らないと日本語になっても難しい。
自分なりにわかりやすい日本語で書かれていて、ページ内検索できるDart言語の紹介ページなんて・・・、あるわけ無く、無いなら作っていくか、という流れでした。

元サイトはDart公式にある A tour of the Dart language です。

追記


A tour of the Dart language

このページでは、変数や演算子からクラスやライブラリに至るまで、Dartの主要な機能を使用する方法を紹介します。より簡単で完全ではない言語の紹介については、language samples pageを参照してください。

Dartのコアライブラリの詳細については、library tourを参照してください。言語機能の詳細については、Dart language specificationを参照してください。

Note: DartPadを使用すると、Dartの言語機能のほとんどを使用することができます(learn more)。Open DartPad。このページでは、例題の一部を表示するために埋め込まれたDartPadを使用しています。DartPadの代わりに空のボックスが表示される場合は、DartPad troubleshooting pageを参照してください。

Dartの基本プログラム

以下のコードは、最も基本的なDartの機能の多くを使用しています。

// 関数の定義
void printInteger(int aNumber) {
  print('The number is $aNumber.'); // コンソールへ出力
}

// ここからアプリの実行が始まる
void main() {
  var number = 42; // 変数の宣言と初期化
  printInteger(number); // 関数の呼び出し
}

ここでは、上記コードで使われているものを紹介します。

//これはコメントです。
一行コメントです。Dart は複数行コメントやドキュメントコメントもサポートしています。詳細はコメントを参照してください。

void
使わない値を示す特殊な型。printInteger()main()のように、明示的に値を返さない関数はvoidリターン型を持ちます。詳細はthis articleを参照してください。

int
整数を示す別の型。追加のbuilt-in-タイプとして、StringList、およびboolがあります。

42
数値リテラル(コード内にかかれている文字や数字)のこと。数値リテラルはコンパイル時定数の一種です。

print()
コンソールやディスプレイに出力します。

'...' (または"...")
文字列リテラル。

$variableName( or${式})
文字列補間: 文字列リテラル内に変数または式を含めます。詳細は文字列を参照ください。

main()
アプリの実行を開始するために必要となる関数です。詳細はmain()関数を参照してください。

var
ゆるい型宣言。

Note: このサイトのコードは、Dart style guideの規約に従っています。

重要な概念

Dart 言語について学ぶ際には、以下の事実と概念を念頭に置いてください。

  • 変数に配置できるものはすべてオブジェクトであり、すべてのオブジェクトはクラスのインスタンスです。数値、関数、nullもオブジェクトです。すべてのオブジェクトは Objectクラスを継承します。
  • Dart は強力な型付けを行っていますが、Dartは型を推測することができるため、型アノテーションはオプションです。冒頭のコードでは、numberint型であると推論されています。型が期待されないことを明示的に伝えたい場合は、特別な型dynamicを使用します。
  • DartはList<int>(整数のリスト)やList<dynamic>(任意の型のオブジェクトのリスト) のような汎用型をサポートしています。
  • Dart はトップレベルの関数 (main()など) だけでなく、クラスやオブジェクトに関連付けられた関数 (それぞれ static メソッドと instance メソッド) もサポートしています。また、関数内に関数を作成することもできます(入れ子になった関数やローカル関数)。
  • 同様に、Dartはトップレベルの変数だけでなく、クラスやオブジェクトに結び付けられた変数 (静的変数とインスタンス変数) もサポートしています。インスタンス変数は、フィールドやプロパティと呼ばれることもあります。
  • Javaとは異なり、Dartにはpublicprotectedprivateというキーワードはありません。識別子がアンダースコア (_) で始まる場合、それはそのライブラリのプライベートです。詳細については、ライブラリと可視性を参照してください。
  • 識別子は、文字またはアンダースコア (_) で始まり、その後にこれらの文字と数字の任意の組み合わせで始まることができます。
  • Dartには、式(実行時の値を持つ)と文(持たない)の両方があります。例えば、条件式 条件 ? expr1 : expr2expr1またはexpr2の値を持ちます。これを値を持たない if-else文と比較してみてください。ステートメントには1つ以上の式が含まれていることがよくありますが、式は直接ステートメントを含むことはできません。
  • Dartツールは警告とエラーの2種類の問題を報告することができます。警告は、コードが動作しない可能性があることを示すものですが、プログラムの実行を妨げるものではありません。エラーにはコンパイル時エラーとランタイムエラーがあります。コンパイル時エラーはコードの実行を妨げ、ランタイムエラーはコードの実行中に例外が発生します。

Note: Dartがpublicやprivateなどのアクセス修飾キーワードの代わりにアンダースコアを使用する理由が気になる場合は、SDK issue 33383 を参照してください。

キーワード

Dart言語がキーワードとして扱うのは下記の通りです。

abstract *2 else import *2 super
as *2 enum in switch
assert export *2 interface *2 sync *1
async *1 extends is this
await *3 extension *2 library *2 throw
break external *2 mixin *2 true
case factory *2 new try
catch false null typedef *2
class final on *1 var
const finally operator *2 void
continue for part *2 while
covariant *2 Function *2 rethrow with
default get *2 return yield *3
deferred *2 hide *1 set *2
do if show *1
dynamic *2 implements *2 static *2

これらの単語を識別子として使用することは避けてください。ただし、必要に応じて、*付きのキーワードは識別子にすることができます。

  • *1 の単語は、特定の場所でのみ意味を持つ文脈上のキーワードです。これは、どこにいても有効な識別子です。
  • *2 は組み込みの識別子です。JavaScriptコードをDartに移植する作業を簡単にするため、これらのキーワードはほとんどの場所で有効な識別子ですが、クラス名や型名、インポートの接頭辞としては使用できません。
  • *3 の単語は、非同期のサポートに関連した限定的な予約語です。async、async*、またはsync*でマークされた関数内では、識別子としてawaitまたはyieldを使用することはできません。

表中の他のすべての単語は予約語であり、識別子にすることはできません。

変数

変数を作成して初期化する例です。

var name = 'Bob';

変数は参照を格納します。nameという変数には、"Bob "という値を持つStringオブジェクトへの参照が格納されています。

name変数の型はStringであると推測されますが、指定することでその型を変更することができます。オブジェクトが単一の型に制限されていない場合は、design guidelinesに従って、Objectまたはdynamic型を指定します。

dynamic name = 'Bob';

もう一つのオプションは、推論される型を明示的に宣言することです。

String name = 'Bob';

Note: このページは、ローカル変数には型注釈ではなくvarを使用するというstyle guide recommendationに従っています。

デフォルト値

初期化されていない変数の初期値はnullです。数値型の変数であっても初期値はnullです。

int lineCount;
assert(lineCount == null);

Note: プロダクションコードでは、assert()の呼び出しは無視されます。一方、開発中は、条件がfalseの場合はassert(condition)が例外をスローします。詳細は Assert を参照してください。

finalとconst

変数を変更するつもりがない場合は、varの代わりに、あるいは型に加えてfinalconstを使用してください。const変数はコンパイル時の定数です。(const変数は暗黙のうちにfinalになります。) finalなトップレベル変数またはクラス変数は、最初に使用されたときに初期化されます。

Note: インスタンス変数にはfinalを指定できますが、constは指定できません。finalなインスタンス変数は、コンストラクタ本体が開始する前に初期化しなければなりません - 変数の宣言時、コンストラクタのパラメータ、あるいはコンストラクタの初期化リストのいずれかで行います。

以下はfinal変数の例です。

final name = 'Bob'; // 型の記載なし
final String nickname = 'Bobby';

final変数を変更することはできません。

name = 'Alice'; // Error: final変数は1度だけセットすることができます

コンパイル時定数にしたい変数にはconstを使用します。const変数がクラス・レベルにある場合は、static constとマークします。変数を宣言する場合は、数値や文字列リテラル、const変数、定数の算術演算の結果などのコンパイル時定数に値を設定します。

const bar = 1000000; // 圧力の単位 (dynes/cm2)
const double atm = 1.01325 * bar; // 標準大気圧

constキーワードは、定数を宣言するためだけのキーワードではありません。定数値を作成したり、定数値を作成するコンストラクタを宣言したりするためにも使用できます。どんな変数でも定数値を持つことができます。

var foo = const [];
final bar = const [];
const baz = []; // `const []`と同等

上記のbazのように、const宣言の初期化式からconstを省略することができます。詳細は DON'T use const redundantlyを参照してください。

それまでにconstの値を持っていたとしても、non-final、non-const変数の値を変更することができます。

foo = [1, 2, 3]; // Was const []

const変数の値を変更することはできません。

baz = [42]; // Error: 定数に値を代入することはできません。

型チェックやキャスト(isas)、コレクションif、スプレッド演算子(......?)を使用する定数を定義できます。

const Object i = 3; // ここで i は int値を持つ const オブジェクトです
const list = [i as int]; // 型キャスト
const map = {if (i is int) i: "int"}; // コレクションif と is
const set = {if (list is List<int>) ...list}; // スプレッド演算子

Note: finalオブジェクトは変更できませんが、そのフィールドは変更できます。それに比べて、constオブジェクトとそのフィールドは変更できません。

constを使用して定数値を作成する方法の詳細については、リストマップ、およびクラスを参照してください。

Built-in タイプ

Dart言語は以下の型を特別にサポートしています。

  • numbers
  • strings
  • booleans
  • lists (配列)
  • sets
  • maps
  • runes (Unicode文字用)
  • symbols

これらの特殊な型のオブジェクトは、リテラルを使って初期化することができます。例えば、'this is a string'は文字列リテラルで、trueは真偽値リテラルです。

Dartのすべての変数はオブジェクト、つまりクラスのインスタンスを参照するので、通常はコンストラクタを使用して変数を初期化することができます。いくつかの組み込み型には、独自のコンストラクタがあります。例えば、Map()コンストラクタを使用してマップを作成することができます。

Numbers

Dartの数字には2つのフレーバーがあります。

int
プラットフォームに応じての、64ビット以下の整数値。DartVMでは、$-2^{63}$から$2^{63}$-1までの値を指定できます。JavaScriptにコンパイルされたDartでは、JavaScriptのNumber型を使用し、$-2^{53}$から$2^{53}$-1までの値を指定できます。

double
IEEE 754規格で規定されている64ビット(倍精度)浮動小数点数。

intdoubleはどちらもnumのサブタイプです。num型には、+, -, /, * などの基本的な演算子があり、abs()ceil()floor()などのメソッドがあります。( >> などのビット単位の演算子はintクラスで定義されています) num とそのサブタイプに探しているものがない場合は、dart:math ライブラリを利用するとよいでしょう。

整数とは、小数点のない数値です。以下に整数リテラルを定義する例を示します。

var x = 1;
var hex = 0xDEADBEEF;

数値に少数が含まれている場合、それは double です。ここでは、double リテラルを定義する例をいくつか紹介します。

var y = 1.1;
var exponents = 1.42e5;

整数リテラルは必要に応じて自動的に倍数に変換されます。

double z = 1; // double z = 1.0 と同等

ここでは、文字列を数字に変換する方法、またはその逆の方法を説明します。

// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

int型は、従来のビットシフト(<<, >>)、AND(&)、OR(|)演算子を指定します。例えば、以下のようになります。

assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111

リテラル数はコンパイル時定数です。オペランドが数値に評価されるコンパイル時定数である限り、多くの算術式もコンパイル時定数です。

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;

String

Dart 文字列は、UTF-16コード単位のシーケンスです。文字列を作成するには、シングルクォートまたはダブルクォートを使用することができます。

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

式の値を文字列の中に入れるには${expression}を使います。式が識別子の場合は{}を省略することができます。オブジェクトに対応する文字列を取得するには、DartはオブジェクトのtoString()メソッドを呼び出します。

var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, ' + 'which is very handy.');
assert('That deserves all caps. ' + '${s.toUpperCase()} is very handy!' ==
    'That deserves all caps. ' + 'STRING INTERPOLATION is very handy!');

Note: ==演算子は、2つのオブジェクトが等価かどうかをテストします。2つの文字列が同じコード単位のシーケンスを含んでいる場合、2つの文字列は等価です。

隣接する文字列リテラルや+演算子を使って文字列を連結することができます。

var s1 = 'String '
    'concatenation'
    " works even over line breaks.";
assert(s1 ==
    'String concatenation works even over '
        'line breaks.');

var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');

複数行の文字列を作成するもう一つの方法:シングルまたはダブルクォーテーションを使ってトリプルクォーテーションを使用します。

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

文字列の前にrを付けることで、"row" の文字列を作成することができます。

var s = r'In a raw string, not even \n gets special treatment.';
// print(s) とすると \n が改行されずに以下が出力される
// In a raw string, not even \n gets special treatment.

Unicode文字を文字列で表現する方法の詳細については、Runesと書記素クラスタ を参照してください。

リテラル文字列は、挿入された式がnullまたは数値、文字列、真偽値として評価されるコンパイル時定数である限り、コンパイル時定数となります。

// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString'; // コンパイル時定数
// const invalidConstString = '$aNum $aBool $aString $aConstList'; // Notコンパイル時定数

文字列の使用についての詳細は、Strings and regular expressions を参照してください。

Booleans

真偽値を表現するために、Dartにはboolという名前の型があります。ブール型を持つオブジェクトは 2つだけです: ブーリアンリテラルのtruefalseで、どちらもコンパイル時の定数です。

Dartの型の安全性は、if (nonbooleanValue)assert (nonbooleanValue)のようなコードを使用できないことを意味します。代わりに、以下のように明示的に値をチェックします。

// 空文字のチェック
var fullName = '';
assert(fullName.isEmpty);

// ゼロのチェック
var hitPoints = 0;
assert(hitPoints <= 0);

// nullのチェック
var unicorn;
assert(unicorn == null);

// NaNのチェック
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

Lists

おそらく、ほぼすべてのプログラミング言語で最も一般的なコレクションは、配列、つまりオブジェクトの順序付けされたグループです。Dartでは、配列はリストオブジェクトなので、ほとんどの人は単にリストと呼んでいます。

Dart のリストリテラルは JavaScript の配列リテラルのように見えます。ここに単純なDartのリストを示します。

var list = [1, 2, 3];

Note: DartはListList<int>型であることを推測しています。このリストにint型以外のオブジェクトを追加しようとすると、アナライザまたはランタイムはエラーを発生させます。詳細は型推論を参照してください。

末尾のカンマ

Dartコレクションリテラルの最後の項目の後にカンマを追加することができます。この末尾のカンマはコレクションには影響しませんが、コピーペーストエラーを防ぐのに役立ちます。(プログラミング言語によっては、最後の項目の後にカンマを追加するとエラーになります)

var list = [
  'Car',
  'Boat',
  'Plane', // ← この ,(カンマ)のこと
];

リストはゼロベースの indexing を使用します。ここで、0は最初の値のインデックスであり、list.length - 1は最後の値のインデックスです。リストの長さを取得したり、リストの値を参照したりすることはJavaScriptと同じようにできます。

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

コンパイル時定数であるリストを作成するには、リストリテラルの前にconstを追加します。

var constantList = const [1, 2, 3];
// constantList[1] = 1; // この行はエラーが発生します

スプレッド演算子

Dart 2.3 ではスプレッド演算子 (...)null対応スプレッド演算子 (...?) が導入され、複数の値をコレクションに挿入する簡潔な方法が提供されました。

例えば、リストのすべての値を別のリストに挿入するために スプレッド演算子 (...) を使用することができます。

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

スプレッド演算子の右にある式がnullになる可能性がある場合は、null対応スプレッド演算子(...?)を使います。

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

スプレッド演算子の詳細や使用例については、spread operator proposal を参照してください。

コレクション演算子

Dart には collection ifcollection for もあり、条件式 (if) と繰り返し (for) を使用してコレクションを構築することができます。

ここでは、3つまたは4つの項目を含むリストを作成するために、collection if を使用する例を示します。

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

ここでは、リストの項目を別のリストに追加する前に、リストの項目を操作するために collection for を使用する例を示します。

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

collection if と collection for の使用例や詳細については、control flow collections proposal を参照してください。

リスト型には、リストを操作するための便利なメソッドがたくさんあります。リストの詳細については、ジェネリクスCollections を参照してください。

Sets

Dartのセットとは、一意のアイテムの順序付けられていないコレクションのことです。Dartのセットのサポートは、セットリテラルとSet型によって提供されます。

ここでは、セットリテラルを使用して作成されたシンプルなDartセットを示します。

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Note: Dartは、halogensSet<String>型を持っていることを推測します。間違った型の値をセットに追加しようとすると、解析または実行時にエラーが発生します。詳細については、type inferenceを参照してください。

空のセットを作成するには、型引数の前に{}を使用するか、またはSet型の変数に{}を代入します。

var names = <String>{};
// Set<String> names = {}; // これはOK
// var names = {}; // これはsetではなくmapとなります

セットかマップか? マップリテラルの構文は、セットリテラルの構文に似ています。マップリテラルが先に来ているため、{}はデフォルトでMap型になっています。もし{}の型アノテーションや割り当てられた変数を忘れてしまった場合、DartはMap<dynamic, dynamic>型のオブジェクトを作成します。

add()メソッドまたはaddAll()メソッドを使用して、既存のセットに項目を追加します。

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

.lengthを使用して、セット内の項目数を取得します。

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

コンパイル時定数であるセットを作成するには、セットリテラルの前にconstを追加します。

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // この行はエラーが発生します

セットは、リストと同様にスプレッド演算子(......?)と collection if と collection for をサポートします。詳細については、リストのスプレッド演算子リストのコレクション演算子の議論を参照してください。

セットの詳細については、ジェネリクスSetsを参照してください。

Maps

一般的に、マップはキーと値を関連付けるオブジェクトです。キーと値はどちらもどのようなタイプのオブジェクトであっても構いません。各キーは一度しか発生しませんが、同じ値を複数回使用することができます。マップのDartサポートは、マップリテラルとMap型によって提供されます。

ここでは、マップリテラルを使用して作成されたシンプルなDartマップをいくつか紹介します。

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

Note: Dartは、giftsの型がMap<String, String>であり、nobleGasesの型がMap<int, String>であることを推論します。どちらかのマップに間違ったタイプの値を追加しようとすると、アナライザまたはランタイムでエラーが発生します。詳細については、type inferenceを参照してください。

Mapコンストラクタを使用して、上記と同じオブジェクトを作成することができます。

var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

Note: C#やJavaのような言語から来た場合、単にMap()ではなくnew Map()が示されることを期待するかもしれません。Dartでは、newキーワードはオプションです。詳細は、コンストラクタの使用 を参照してください。

JavaScriptの場合と同様に、既存のマップに新しいキーと値のペアを追加します。

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

JavaScriptと同じ方法でマップから値を取得します。

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

マップにないキーを探すと、nullが返されます。

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

.lengthを使用して、マップ内のキーと値のペアの数を取得します。

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

コンパイル時定数であるマップを作成するには、マップリテラルの前にconstを追加します。

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // この行はエラーが発生します

マップは、リストのようにスプレッド演算子(......?)と collection if と collection for をサポートしています。詳細と例については、spread operator proposal control flow collections proposal の提案を参照してください。

マップの詳細については、ジェネリクスMaps を参照してください。

Runesとgrapheme clusters(書記素クラスタ)

Dartでは、runesは文字列のUnicodeコードポイント(符号位置)を公開します。characters packageを使用して、Unicode (extended) grapheme clustersとしても知られるユーザーが認識した文字を表示したり操作したりすることができます。

Unicodeは、世界中のすべての文字システムで使用されている各文字、数字、および記号に固有の数値を定義しています。Dart文字列はUTF-16コード単位のシーケンスであるため、文字列内でUnicode コードポイントを表現するには特殊な構文が必要です。Unicodeコードポイントを表現する通常の方法は、\uXXXXで、ここでXXXXは 4桁の16進数の値です。例えば、ハート文字 ♥ は、\u2665です。4桁以外の16進数を指定する場合は、中括弧内に値を入れます。例えば、笑う絵文字 😆 は、\u{1f606}です。

個々のUnicode文字を読み書きする必要がある場合は、キャラクタパッケージでStringに定義されているcharactersゲッターを使用します。返されるCharactersオブジェクトは、文字列を書記素クラスタのシーケンスとして表したものです。以下にcharacters APIの使用例を示します。

import 'package:characters/characters.dart';
...
var hi = 'Hi 🇩🇰';
print(hi);
print('The end of the string: ${hi.substring(hi.length - 1)}');
print('The last character: ${hi.characters.last}\n');

環境にもよりますが、出力は以下のようになります。

$ dart bin/main.dart
Hi 🇩🇰
The end of the string: ???
The last character: 🇩🇰

文字パッケージを使用して文字列を操作する方法の詳細については、文字パッケージのexampleAPI reference を参照してください。

Symbols

Symbol オブジェクトは、Dart プログラムで宣言された演算子または識別子を表します。シンボルを使用する必要はないかもしれませんが、名前で識別子を参照するAPIでは非常に重要です。

識別子のシンボルを取得するには、シンボルリテラルを使用します。#の後に識別子が続きます。

#radix
#bar

シンボルリテラルはコンパイル時の定数です。

関数

Dartは真のオブジェクト指向言語なので、関数であってもオブジェクトであり、Function という型を持っています。つまり、関数は変数に代入したり、他の関数の引数として渡すことができます。また、Dartクラスのインスタンスをあたかも関数であるかのように呼び出すこともできます。詳細は、呼び出し可能なクラスを参照してください。

ここでは、関数の実装例を示します。

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Effective Dartでは type annotations for public APIs を推奨していますが、型を省略してもこの関数は動作します。

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

式を1つだけ含む関数の場合は、短縮構文を使用することができます。

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

=> expr構文は{ return expr; }の省略形です。=> 記法は、アロー構文、アロー関数と呼ばれることもあります。

Note: 矢印 (=>) とセミコロン (;) の間に入ることができるのは、文ではなく式だけです。たとえば、if文を記述することはできませんが、条件式 を使用することはできます。

パラメーター

関数は、必要な位置指定パラメータを任意の数だけ持つことができます。これらのパラメータの後には、名前付きパラメータまたはオプションの位置指定パラメータが続きます(両方ではありません)。

Note: いくつかのAPI(特にFlutterウィジェットのコンストラクタ)では、必須のパラメータであっても名前付きパラメータのみを使用します。詳細は次のセクションを参照してください。

関数に引数を渡すとき、または関数のパラメータを定義するときには、末尾のカンマ を使用することができます。

名前付きパラメータ

名前付きパラメータは、特に必要とマークされていない限りオプションです。

関数を呼び出す際には、paramName: valueを使用して名前付きパラメータを指定することができます。例えば、以下のようになります。

enableFlags(bold: true, hidden: false);

関数を定義する際には、{param1, param2, ...}を使用して名前付きパラメータを指定します。

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

名前付きパラメータはオプションのパラメータの一種ですが、@required というアノテーションを付けることで、そのパラメータが必須のパラメータであること、つまりユーザがパラメータに値を指定しなければならないことを示すことができます。たとえば、次のようになります。

const Scrollbar({Key key, @required Widget child})

誰かが引数childを指定せずにScrollbarを作成しようとすると、アナライザは問題を報告します。

@required を使用するには、meta パッケージに依存するため、package:meta/meta.dartをインポートします。

オプションの定位置パラメータ

関数パラメータのセットを[]で包むと、それらはオプションの定位置パラメータとしてマークされます。

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

オプションのパラメータを指定せずにこの関数を呼び出す例を示します。

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

そして、3番目のパラメータを指定してこの関数をコールする例を示します。

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

パラメータのデフォルト値

=を使用して、名前付きパラメータと定位置パラメータの両方のデフォルト値を定義することができます。デフォルト値はコンパイル時の定数でなければなりません。デフォルト値が指定されていない場合、デフォルト値はnullとなります。

ここでは、名前付きパラメータにデフォルト値を設定する例を示します。

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold は true となり、hidden は false となる
enableFlags(bold: true);

Deprecation note: 古いコードでは、名前付きパラメータのデフォルト値を設定するために=の代わりにコロン (:) を使用している場合があります。これは元々、名前付きパラメータには:のみがサポートされていたからです。このサポートは廃止されるかもしれないので、デフォルト値を指定するには=を使用することをお勧めします。

次の例では、定位置パラメータに既定値を設定する方法を示します。

String say(String from, String msg,
    [String device = 'carrier pigeon']) {
  var result = '$from says $msg with a $device';
  return result;
}

assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');

デフォルト値としてリストやマップを渡すこともできます。次の例は、関数doStuff()を定義したもので、listパラメータにはデフォルトのリストを、giftsパラメータにはデフォルトのマップを指定します。

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}

main()関数

すべてのアプリは、アプリの入り口となるトップレベルのmain()関数を持つ必要があります。main()関数はvoidを返し、引数にはオプションのList<String>パラメータがあります。

ここでは、Webアプリのmain()関数の例を示します。

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

Note: 前述のコードの..構文は カスケード と呼ばれています。カスケードでは、単一のオブジェクトのメンバに対して複数の操作を実行できます。

ここでは、引数を受け取るコマンドラインアプリのmain()関数の例を示します。

// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

args library を使用して、コマンドライン引数を定義したり解析したりすることができます。

第1級クラスオブジェクトとしての関数

関数を別の関数のパラメータとして渡すことができます。例えば、以下のようになります。

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);

下記のように、変数に関数を代入することもできます。

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

この例では無名関数を使用しています。これらについては次のセクションで説明します。

無名関数

ほとんどの関数は、main()printElement()のように名前が付けられています。無名関数と呼ばれる名前のない関数を作成することもできますし、ラムダやクロージャを作成することもあります。無名関数を変数に代入することで、例えばコレクションに追加したり削除したりすることができます。

無名関数は名前付き関数に似ています。括弧で囲まれた、カンマとオプションの型アノテーションで区切られた0個以上のパラメータです。

以下のコードブロックには、関数の本体が含まれています。

([[Type] param1[, ]]) {
  codeBlock;
};

以下の例では、型なしのパラメータitemを持つ無名関数を定義しています。この関数は、リスト内の各項目に対して呼び出され、指定されたインデックスの値を含む文字列を表示します。

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

以下をDart padなどで実行してみてください。

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}

関数に1つのステートメントしか含まれていない場合は、アロー演算子を使用して短縮することができます。以下の行をDart padに貼り付けて実行をクリックし、関数的に同等であることを確認してください。

list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));

静的スコープ

Dartは語彙的にスコープされた言語であり、変数のスコープは静的に決定され、単にコードのレイアウトによって決定されます。変数がスコープ内にあるかどうかは「中括弧を外側にたどって」確認することができます。

ここでは、各スコープレベルで変数を入れ子にした関数の例を示します。

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

nestedFunction()は、すべてのレベルからトップレベルまでの変数を使用できることに注目してください。

静的クロージャ

クロージャとは、関数が元のスコープ外で使用されている場合でも、そのレキシカルスコープ内の変数にアクセスできる関数オブジェクトのことです。

関数は、周囲のスコープで定義された変数の上で閉じることができます。次の例では、makeAdder()は変数addByを捕捉しています。返された関数は、どこに行ってもaddByを記憶しています。

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

関数の等価性のテスト

ここでは、トップレベルの関数、静的メソッド、インスタンスメソッドの等価性をテストする例を示します。

void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}

戻り値

すべての関数は値を返します。戻り値が指定されていない場合、return null;文が暗黙的に関数本体に追加されます。

foo() {}

assert(foo() == null);

演算子

Dart は、次の表に示す演算子をサポートしています。これらの演算子の多くをクラスメンバとして実装することができます。

Description Operator
単項後置 expr++ expr-- ( ) [ ] . ?.
単項前置 -expr !expr ~expr ++expr --expr await expr
乗除 * / % ~/
加減 + -
シフト << >> >>>
ビットAND &
ビットXOR ^
ビットOR
比較と型チェック >= > <= < as is is!
等価 == =!
論理AND &&
論理OR ||
nullチェック ??
条件式 expr1 ? expr2 : expr3
カスケード ..
代入 = *= /= += -= &= ^= etc..

Warning: 演算子の優先順位は、Dartパーサの動作の近似値です。決定的な答えについては、Dart language specificationを参照してください。

演算子を使用すると、式を作成します。演算子式の例をいくつか挙げます。

a++
a + b
a = b
a == b
c ? a : b
a is T

演算子テーブルでは、各演算子は下の行の演算子よりも優先順位が高くなります。例えば、乗算演算子%は、論理AND演算子&&よりも優先度が高い、等価演算子==よりも優先度が高い (つまり、&&==よりも前に実行される) ことになります。この優先順位は、以下の2行のコードが同じように実行されることを意味します。

// 括弧をつけて読みやすさを向上
if ((n % i == 0) && (d % i == 0)) ...

// 括弧なしで読みにくいけど上記と同等
if (n % i == 0 && d % i == 0) ...

Warning: 2つのオペランドを取る演算子では、左端のオペランドがどのメソッドを使用するかを決定します。たとえば、VectorオブジェクトとPointオブジェクトがある場合、aVector + aPointVectorの加算 (+) を使用します。

算術演算子

Dartは、次の表に示すように、通常の算術演算子をサポートしています。

Operator Meaning
+ 加算
- 減算
-expr 単項マイナス、また否定としても知られる(式の符号を逆にする)
* 乗算
/ 除算
~/ 除算(整数の結果を返す)
% 整数の除算の剰余(あまり)

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // double型
assert(5 ~/ 2 == 2);  // int型
assert(5 % 2 == 1);   // あまり

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

Dartは、前置と後置のインクリメントとデクリメントの両方の演算子もサポートしています。

Operator Meaning
++var var = var + 1(式の値は var + 1)
var++ var = var + 1(式の値は var)
--var var = var - 1(式の値は var - 1)
var-- var = var - 1(式の値は var)

var a, b;

a = 0;
b = ++a; // bが値を取得する前にaをインクリメント
assert(a == b); // 1 == 1

a = 0;
b = a++; // bが値を取得した後にaをインクリメント
assert(a != b); // 1 != 0

a = 0;
b = --a; // bが値を取得する前にaをデクリメント
assert(a == b); // -1 == -1

a = 0;
b = a--; // bが値を取得した後にaをデクリメント
assert(a != b); // -1 != 0

等号と関係演算子

次の表では、等号と関係演算子の意味を示しています。

Operator Meaning
== 等価: 下記参照
!= 不等価
> 超える
< 未満
>= 以上
<= 以下

2つのオブジェクト x と y が同じものを表しているかどうかを調べるには==演算子を使用します。(まれに、2つのオブジェクトが全く同じオブジェクトであるかどうかを知りたい場合は、代わりに identical() 関数を使用します)。==演算子の動作は次のようになります。

  1. x または y が null の場合において、両方が null の場合は true を返す。片方だけが null の場合は false を返します。
  2. メソッド呼び出しx.==(y)の結果を返します。(そう、==のような演算子は、最初のオペランドで呼び出されるメソッドです。詳細は、演算子 を参照してください)。

以下に、等価演算子と関係演算子のそれぞれの使用例を示します。

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

型テスト演算子

as,is,is!演算子は、実行時に型をチェックするのに便利です。

Operator Meaning
as 型変換(ライブラリのプレフィックス を指定するのにも使われる)
is オブジェクトが指定された型の場合は true
is! オブジェクトが指定された型の場合は false

obj is Tの結果は、objTで指定されたインターフェイスを実装していればtrueです。

as演算子を使用して、オブジェクトが特定の型であることが確実な場合にのみ、オブジェクトを特定の型にキャストします。例は次のとおりです。

(emp as Person).firstName = 'Bob';

オブジェクトがT型であることがわからない場合は、オブジェクトを使用する前にis Tを使用して型を確認します。

if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}

Note: 2つのコードは同じではありません。empが null であるかPersonでない場合、最初の例では例外が発生しますが、2番目の例では例外は発生しません。

代入演算子

すでに見てきたように、=演算子を使って値を代入することができます。代入先の変数がnullの場合のみ代入するには、??=演算子を使用します。

// a に値を代入
a = value;
// b が null のときだけ 値を代入(b が null ではないとき、何もしない)
b ??= value;

+=などの複合代入演算子は、演算と代入を組み合わせたものです。

= -= /= %= >>= ^=
+= *= ~/= <<= &= |=

ここでは、複合代入演算子がどのように動作するかを説明します。

複合代入 同じの表現
演算子opの場合 a op= b a = a op b
a += b a = a + b

次の例では、代入演算子と複合代入演算子を使用しています。

var a = 2; // = で代入
a *= 3; // a = a * 3
assert(a == 6);

論理演算子

論理演算子を使用して、真偽式を反転させたり、結合したりすることができます。

Operator Meaning
!expr 式を反転させる
|| 論理OR
&& 論理AND

論理演算子の使用例です。

if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

ビット演算子とシフト演算子

Dartでは、数値の個々のビットを操作することができます。通常、これらのビット演算子やシフト演算子を整数で使用します。

Operator Meaning
& AND
| OR
^ XOR
~expr 補数
<< 左シフト
>> 右シフト

ビット演算子とシフト演算子の使用例です。

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // 左シフト
assert((value >> 4) == 0x02); // 右シフト

条件式

Dartには2つの演算子があり、if-else文が必要になるかもしれない式を簡潔に評価することができます。

条件 ? expr1 : expr2

条件がtrueの場合はexpr1を評価し(その値を返します)、そうでない場合はexpr2を評価してその値を返します。

expr1 ?? expr2

expr1がnullでない場合はexpr1を返し、そうでない場合はexpr2の値を評価して返します。
ブール式に基づいて値を代入する必要がある場合は、? :の使用を検討してください。

var visibility = isPublic ? 'public' : 'private';

真偽式がnullをテストする場合は、??の使用を検討してください。

String playerName(String name) => name ?? 'Guest';

上記例は、下記の2つの書き方でもよかったのですが簡潔には書けませんでした。

// 少し長い例 ?: 演算子を使用
String playerName(String name) => name != null ? name : 'Guest';

// とても長い例 if-else 演算子を使用
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}

カスケード記法(..)

カスケード(..)を使用すると、同じオブジェクトに対して一連の操作を行うことができます。関数呼び出しに加えて、同じオブジェクトのフィールドにアクセスすることもできます。これにより、一時的な変数を作成する手間が省け、より流動的なコードを書くことができます。

次のコードを考えてみましょう。

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

最初のメソッドquerySelector()の呼び出しは、セレクタオブジェクトを返します。カスケード表記に続くコードは、このセレクタ・オブジェクトを操作し、それ以降に返される可能性のある値は無視します。

先ほどの例は次のようになります。

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();

実際のオブジェクトを返す関数の上にカスケードを構築するように注意してください。例えば、以下のコードは失敗します。

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.

sb.write()はvoidを返しており、void上にカスケードを構築することはできません。

Note: 厳密に言えば、カスケードの「ダブルドット」表記は演算子ではありません。これはDart構文の一部です。

その他の演算子

残りのオペレータのほとんどは他の例で見てきましたね。

Operator Name Meaning
( ) 関数アプリケーション 関数呼出しを表す
[ ] リストアクセス リスト内の指定されたインデックスの値を参照します
. メンバアクセス 式のプロパティを参照します。例: foo.bar は、式 foo からプロパティ bar を選択します。
?. 条件付きメンバアクセス .と同様ですが、左端のオペランドはnullにすることができます; 例: foo?.barは、fooがnullでない限り、式fooからプロパティbarを選択します (この場合、foo?.barの値はnullになります)。

. ?. ..演算子の詳細については、クラス を参照してください。

制御フロー文

以下のいずれかを使用して、Dartコードのフローを制御することができます。

  • ifelse
  • forループ
  • while, do-whileループ
  • breakcontinue
  • switchcase
  • assert

例外で説明したように、try-catchthrowを使って制御フローに影響を与えることもできます。

ifとelse

Dartは、次のサンプルで示されているように、オプションのelse文を含むif文をサポートしています。条件式も参照してください。

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

JavaScriptとは異なり、条件はブール値を使用しなければなりません。詳細は Booleans を参照してください。

forループ

標準のforループで反復処理を行うことができます。例えば、以下のようになります。

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Dartのforループ内部のクロージャはインデックスの値をキャプチャし、JavaScriptによくある落とし穴を回避します。例えば、次のように考えてみましょう。

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

出力は、期待通りに0となり、その後1が出力されます。対照的に、この例では、JavaScriptでは2を出力し、その後2を出力します。
反復処理を行うオブジェクトがイテレート可能な場合は、forEach()メソッドを使用することができます。現在の反復処理カウンタを知る必要がない場合は、forEach()を使用するのが良いでしょう。

candidates.forEach((candidate) => candidate.interview());

ListやSetのような反復可能なクラスもfor-in形式のiterationをサポートしています。

var collection = [1, 2, 3];
for (var x in collection) {
  print(x); // 1 2 3
}

while と do-while

whileループはループの前に条件を評価します。

while (!isDone()) {
  doSomething();
}

do-whileループは、ループの後に条件を評価します。

do {
  printLine();
} while (!atEndOfPage());

break と continue

ループを止めるにはbreakを使用します。

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

次のループの繰り返しをスキップするにはcontinueを使用します。

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

リストやセットのような lterable を使用している場合は、その例を別の方法で記述することができます。

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());

switch と case

Dartのスイッチ文は、整数、文字列、コンパイル時定数を==を使用して比較します。比較されるオブジェクトはすべて同じクラスのインスタンスである必要があり(そのサブタイプのインスタンスではない)、クラスは==をオーバーライドしてはなりません。列挙型switchステートメントでうまく機能します。

空でないcase句は、原則としてbreakで終わります。空でないcase句を終了させる他の有効な方法は、continuethrowreturnです。

case句にマッチするものがない場合にコードを実行するには、default句を使用します。

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

次の例では、case句のbreakが省略されているため、エラーが発生します。

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break

  case 'CLOSED':
    executeClosed();
    break;
}

しかし、Dartは空のcase句をサポートしており、フォールスルー形式を可能にしています。

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

どうしてもフォールスルーが必要な場合は、continueとラベルを使うことができます。

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;
}

case句はローカル変数を持つことができ、それはその句のスコープ内でのみ表示されます。

assert

開発中にアサート文 -assert(condition, optionalMessage);- を使用して、真偽条件がfalseの場合に通常の実行を中断させます。このツアーでは、アサート文の例を見ることができます。以下にいくつかの例を紹介します。

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

アサーションにメッセージを添付するには、assertの第2引数に文字列を追加します (オプションで末尾にカンマを付けることもできます)。

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

assertの第一引数には、真偽値で解決する任意の式を指定できます。式の値がtrueの場合、アサーションは成功し、実行は続行されます。false の場合、アサーションは失敗し、例外(AssertionError)がスローされます。

アサーションはいつ実行されるのでしょうか?それは使用しているツールやフレームワークによります。

  • Flutterはdebug modeのときアサーションが有効になります。
  • dartdevcのような開発専用のツールは、通常デフォルトでアサーションが有効になります。
  • dartやdart2jsのようないくつかのツールは、コマンドラインフラグ--enable-assertsを使ってアサーションをサポートしています。

実運用のコードでは、アサーションは無視され、アサーションの引数は評価されません。

例外 Exceptions

Dartコードでは、例外をスローしたりキャッチしたりすることができます。例外とは、予期せぬことが起こったことを示すエラーです。例外が捕捉されない場合、例外を発生させたアイソレートは中断され、アイソレートとそのプログラムは終了します。

Javaとは対照的に、Dartの例外はすべてチェックされていない例外です。メソッドは、どのような例外を投げるかを宣言しないので、例外をキャッチする必要はありません。

Dartには、ExceptionErrorの型と、多数の定義済みのサブタイプがあります。もちろん、独自の例外を定義することもできます。しかし、Dartプログラムは、例外とエラー・オブジェクトだけでなく、非nullオブジェクトを例外として投げることができます。

throw

ここでは、例外を投げる、または発生させる例を示します。

throw FormatException('Expected at least 1 section');

任意のオブジェクトを投げることもできます。

throw 'Out of llamas!';

Note: プロダクション品質のコードでは、通常ErrorExceptionを実装した型をスローします。

例外を投げるのは式なので、例外を投げるのは => 文の中だけでなく、式を許可している他の場所でも可能です。

void distanceTo(Point other) => throw UnimplementedError();

catch

例外をキャッチする、またはキャプチャすることで、例外の伝播を止めることができます (例外を再スローしない限り)。例外をキャッチすることで、例外を処理する機会を得ることができます。

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

複数のタイプの例外を投げることができるコードを処理するために、複数のcatch句を指定することができます。投げられたオブジェクトの型にマッチする最初の catch句が例外を処理します。catch句が型を指定していない場合、その節は、スローされるオブジェクトの任意の型を処理できます。

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

前述のコードにあるように、oncatchのどちらか、または両方を使用することができます。onは例外の種類を指定する必要がある場合に使用します。catchは、例外ハンドラが例外オブジェクトを必要とする場合に使用します。

catch()には1つまたは2つのパラメータを指定することができます。1つ目はスローされた例外で、2つ目はスタックトレース(StackTraceオブジェクト)です。

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

例外を部分的に処理しながら伝播を許可するには、rethrowキーワードを使用します。

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

finally

例外がスローされるかどうかに関わらず、いくつかのコードが確実に実行されるようにするには、 finally句を使用します。catch句が例外にマッチするものがない場合、例外はfinally句が実行された後に伝搬されます。

try {
  breedMoreLlamas();
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

最後の句は、マッチするcatch句の後に実行されます。

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}

ライブラリツアーのExceptionのセクションを読むことで、さらに詳しく知ることができます。

クラス

Dartはクラスとミックスインベースの継承を持つオブジェクト指向言語です。すべてのオブジェクトはクラスのインスタンスであり、すべてのクラスはObjectから派生します。ミックスインベース継承とは、(Objectを除く) すべてのクラスには 1つのスーパークラスがありますが、クラス本体は複数のクラス階層で再利用できることを意味します。拡張メソッドは、クラスを変更したりサブクラスを作成したりすることなく、クラスに機能を追加する方法です。

クラスメンバの使用

オブジェクトには、関数とデータ(それぞれメソッドとインスタンス変数)からなるメンバがあります。メソッドを呼び出すと、オブジェクト上でそれを呼び出すことになります。

インスタンス変数やメソッドを参照するには、ドット(.)を使用します。

var p = Point(2, 2);

// Get the value of y.
assert(p.y == 2);

// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));

.の代わりに?.を使用して、左端のオペランドがnullの場合の例外を回避します。

// If p is non-null, set a variable equal to its y value.
var a = p?.y;

コンストラクタの使用

コンストラクタを使用してオブジェクトを作成することができます。コンストラクタ名には、ClassNameまたはClassName.identifierのいずれかを指定できます。例えば、次のコードは Point()Point.fromJson()コンストラクタを使用してPointオブジェクトを作成しています。

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

次のコードも同じ効果がありますが、コンストラクタ名の前にオプションのnewキーワードを使用しています。

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

Version note: newキーワードはDart 2でオプションになりました。

一部のクラスでは、定数コンストラクタを提供しています。定数コンストラクタを使用してコンパイル時定数を作成するには、コンストラクタ名の前にconstキーワードを付けます。

var p = const ImmutablePoint(2, 2);

2つの同一のコンパイル時定数を構築すると、1つの正規のインスタンスになります。

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

定数コンテキスト内では、コンストラクタやリテラルの前にconstを省略することができます。例えば、以下のコードを見てください。

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

constキーワードの最初の使用以外はすべて省略できます。

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

定数コンストラクタが定数コンテキストの外にあり、constを使用せずに呼び出された場合、定数ではないオブジェクトが作成されます。

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

Version note: Dart 2では、constキーワードは定数コンテキスト内ではオプションになりました。

オブジェクトタイプの取得

実行時にオブジェクトの型を取得するには、Typeオブジェクトを返すObjectのruntimeTypeプロパティを使用することができます。

print('The type of a is ${a.runtimeType}');

ここまででクラスの使い方を見てきました。残りの部分では、クラスの実装方法を説明します。

インスタンス変数

インスタンス変数の宣言方法は以下の通りです。

class Point {
  double x; // インスタンス変数 x を宣言, null で初期化
  double y; // インスタンス変数 y を宣言, null で初期化
  double z = 0; // インスタンス変数 z を宣言, 0 で初期化
}

初期化されていないすべてのインスタンス変数は、値がnullになります。

すべてのインスタンス変数は暗黙のゲッターメソッドを生成します。非初期化のインスタンス変数も暗黙のセッターメソッドを生成します。詳細は、ゲッターとセッターを参照してください。

class Point {
  double x;
  double y;
}

void main() {
  var point = Point();
  point.x = 4; // x のセッターメソッドを使用
  assert(point.x == 4); // x のゲッターメソッドを使用
  assert(point.y == null); // default値は null
}

インスタンス変数を宣言された場所で(コンストラクタやメソッドではなく)初期化した場合、インスタンスが作成されたときに値が設定されます。

コンストラクタ

クラスと同じ名前の関数を作成してコンストラクタを宣言します(さらに、オプションで、名前付きコンストラクタで説明されている追加の識別子)。コンストラクタの最も一般的な形式である生成型コンストラクタは、クラスの新しいインスタンスを作成します。

class Point {
  double x, y;

  Point(double x, double y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

thisキーワードは、現在のインスタンスを参照します。

Note: thisは、名前の競合がある場合にのみ使用します。そうでない場合、Dartスタイルではthisが省略されます。

コンストラクタの引数をインスタンス変数に代入するパターンは非常に一般的であるため、Dartにはそれを簡単にするためのシンタックスシュガーがあります。

class Point {
  double x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

デフォルトのコンストラクタ

コンストラクタを宣言しない場合は、デフォルトのコンストラクタが用意されています。デフォルトのコンストラクタは引数を持たず、スーパークラスの引数なしのコンストラクタを呼び出します。

コンストラクタは継承されない

サブクラスはスーパークラスからコンストラクタを継承しません。コンストラクタを宣言していないサブクラスは、デフォルトの (引数なし、名前なしの) コンストラクタのみを持ちます。

名前付きコンストラクタ

クラスの複数のコンストラクタを実装したり、わかりやすくするために名前付きコンストラクタを使用します。

class Point {
  double x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

コンストラクタは継承されないことを覚えておいてください。つまり、スーパークラスの名前付きコンストラクタはサブクラスに継承されないということです。スーパークラスで定義された名前付きコンストラクタを使用してサブクラスを作成したい場合は、サブクラスでそのコンストラクタを実装する必要があります。

デフォルトではないスーパークラスのコンストラクタの呼び出し

標準では、サブクラスのコンストラクタは、スーパークラスの名前のない引数なしのコンストラクタを呼び出します。スーパークラスのコンストラクタは、コンストラクタ本体の先頭で呼び出されます。初期化リストも使用している場合は、スーパークラスが呼び出される前に実行されます。まとめると、実行順は以下のようになります。

  1. 初期化リスト
  2. スーパークラスの引数なしコンストラクタ
  3. メインクラスの引数なしコンストラクタ

スーパークラスに名前のない引数なしのコンストラクタがない場合は、スーパークラスのコンストラクタのいずれかを手動で呼び出す必要があります。スーパークラスのコンストラクタは、コロン (:) の後、コンストラクタ本体 (ある場合) の直前に指定します。

次の例では、Employee クラスのコンストラクタは、そのスーパークラス Person の名前付きコンストラクタを呼び出します。以下のコードを、Dart pad等で実行してください。

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

スーパークラスのコンストラクタへの引数は、コンストラクタを呼び出す前に評価されるため、引数には関数呼び出しなどの式を指定することができます。

class Employee extends Person {
  Employee() : super.fromJson(defaultData);
  // ···
}

Warning: スーパークラスのコンストラクタの引数はthisにアクセスできません。例えば、引数は静的メソッドを呼び出すことができますが、インスタンスメソッドを呼び出すことはできません。

初期化リスト

スーパークラスのコンストラクタを呼び出す以外にも、コンストラクタ本体が実行される前にインスタンス変数を初期化することもできます。初期化リストはカンマで区切ってください。

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

Warning: 初期化子の右側はthisにアクセスできません。

開発中は、初期化リストでassertを使用して入力を検証することができます。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

初期化リストは、最終フィールドを設定するときに便利です。次の例では、初期化リストで3つの最終フィールドを初期化しています。Dart pad等でコードを実行してください。

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}

コンストラクタのリダイレクト

コンストラクタの目的は、同じクラスの別のコンストラクタにリダイレクトすることだけということもあります。リダイレクトするコンストラクタのボディは空で、コロン (:) の後にコンストラクタコールが表示されます。

class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

定数コンストラクタ

クラスが変更されないオブジェクトを生成する場合、これらのオブジェクトをコンパイル時の定数にすることができます。これを実現するには、constコンストラクタを定義し、すべてのインスタンス変数がfinalであることを確認します。

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

定数コンストラクタは常に定数を作成するわけではありません。詳細については、コンストラクタの使用 を参照してください。

ファクトリコンストラクタ

必ずしもクラスの新しいインスタンスを生成しないコンストラクタを実装する場合は、factoryキーワードを使用します。たとえば、ファクトリー・コンストラクタはキャッシュからインスタンスを返したり、サブタイプのインスタンスを返したりします。ファクトリー コンストラクタのもう一つの使用例は、初期化リストで処理できないロジックを使用してfinal変数を初期化することです。

次の例では、Loggerファクトリコンストラクタはキャッシュからオブジェクトを返し、Logger.fromJsonファクトリコンストラクタはJSONオブジェクトからfinal変数を初期化します。

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) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

Note: ファクトリコンストラクタはthisにアクセスできません。

他のコンストラクタと同じようにファクトリーコンストラクタを呼び出してください。

var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

メソッド

メソッドは、オブジェクトの動作を提供する関数です。

インスタンスメソッド

オブジェクト上のインスタンスメソッドは、インスタンス変数とthisにアクセスすることができます。以下のサンプルのdistanceTo()メソッドはインスタンスメソッドの例です。

import 'dart:math';

class Point {
  double x, y;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

演算子

演算子は、特別な名前を持つインスタンスメソッドです。Dartでは、以下の名前の演算子を定義することができます。

< * | [ ]
> / ^ [ ]=
<= ~/ & ~
>= * << ==
- % >>

Note: !=のようないくつかの演算子が名前のリストにないことに気がついたかもしれません。これは、それらが単なるシンタックスシュガーだからです。例えば、e1 != e2という式は!(e1 == e2)のシンタックスシュガーです。

演算子宣言は、組み込みの識別子演算子を使用して識別されます。次の例では、ベクトルの加算 (+) と減算 (-) を定義しています。

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.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

ゲッターとセッター

ゲッターとセッターは、オブジェクトのプロパティへの読み書きアクセスを提供する特別なメソッドです。各インスタンス変数には暗黙のゲッターと、必要に応じてセッターがあることを覚えておいてください。getおよびsetキーワードを使用してゲッターとセッターを実装することで、追加のプロパティを作成することができます。

class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

ゲッターとセッターを使用すると、インスタンス変数から始めて、後からメソッドでラップすることができます。すべてクライアントコードを変更する必要はありません。

Note: インクリメント (++) のような演算子は、ゲッターが明示的に定義されているかどうかに関わらず、期待される方法で動作します。予期せぬ副作用を避けるために、演算子はゲッターを一度だけ呼び出し、その値を一時変数に保存します。

抽象メソッド

インスタンス、ゲッター、およびセッター・メソッドは抽象化することができ、インターフェイスを定義しますが、その実装は他のクラスに任せることができます。抽象メソッドは、抽象クラス にのみ存在できます。

メソッドを抽象化するには、メソッド本体の代わりにセミコロン (;) を使用します。

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

抽象クラス

抽象クラスを定義するにはabstract修飾子を使用します。抽象クラスは、インターフェイスを定義するのに便利で、多くの場合は何らかの実装が含まれています。抽象クラスをインスタンス化できるように見せたい場合は、ファクトリー コンストラクタ を定義します。

抽象クラスはしばしば抽象メソッド を持っています。ここでは、抽象メソッドを持つ抽象クラスを宣言する例を示します。

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

暗黙的インターフェイス

すべてのクラスは暗黙のうちに、そのクラスのすべてのインスタンスメンバと、それが実装するすべてのインタフェースを含むインタフェースを定義しています。Bの実装を継承せずにクラスBのAPIをサポートするクラスAを作成したい場合、クラスAはBのインターフェースを実装しなければなりません。

クラスは、1つ以上のインターフェースをimplements句で宣言し、そのインターフェースが必要とするAPIを提供することで、1つ以上のインターフェースを実装します。例えば、以下のようになります。

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

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

// An implementation of the Person interface.
class Impostor implements Person {
  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()));
}

クラスが複数のインタフェースを実装していることを指定した例です。

class Point implements Comparable, Location {...}

クラスの拡張

サブクラスを作成するにはextendsを使用し、スーパークラスを参照するにはsuperを使用します。

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

メンバーのオーバーライド

サブクラスは、インスタンス・メソッド (演算子 を含む)、ゲッター、およびセッターをオーバーライドできます。@overrideアノテーションを使用して、意図的にメンバーをオーバーライドしていることを示すことができます。

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

type safe なコード内のメソッド・パラメータやインスタンス変数の型を絞り込むには、covariant keyword を使用します。

Warning: ==をオーバーライドする場合は、ObjectのhashCodeゲッターもオーバーライドする必要があります。==およびhashCodeをオーバーライドする例は、Implementing map keys を参照してください。

noSuchMethod()

コードが存在しないメソッドやインスタンス変数を使おうとたびに検出したり反応したりするには、 noSuchMethod()をオーバーライドすることができます。

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

以下のいずれかが真でない限り、未実装のメソッドを呼び出すことはできません。

  • レシーバが静的型のdynamic型を持っている。
  • レシーバが未実装メソッドを定義する静的型を持っている(抽象型でもOK)、かつ、レシーバの動的型がObjectクラスのものとは異なるnoSuchMethod()の実装を持っている。

詳細については、非公式の noSuchMethod forwarding specification を参照してください。

拡張メソッド

拡張メソッドとは、既存のライブラリに機能を追加する方法です。知らず知らずのうちに拡張メソッドを使っているかもしれません。例えば、IDE でコード補完を使用する場合、通常のメソッドと並んで拡張メソッドを提案してくれます。

ここでは、string_apis.dartで定義されているparseInt()という名前のStringの拡張メソッドを使用した例を示します。

import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

拡張メソッドの使用方法や実装の詳細は、extension methods page を参照してください。

列挙型

列挙型は一定数の定数値を表すために使用される特殊な種類のクラスです。

列挙型の使用

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

enum Color { red, green, blue }

列挙型を宣言する際には、末尾のカンマを使用することができます。

列挙型の各値にはindexゲッターがあり、列挙型宣言の値のゼロを基準とした位置を返します。例えば、1番目の値はインデックス0、2番目の値はインデックス1となります。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

列挙型のすべての値のリストを取得するには、列挙型のvalues定数を使用します。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

switch文 で列挙型を使うことができますが、列挙型のすべての値を扱わないと警告が出ます。

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

列挙型には以下の制限があります。

  • 列挙型のサブクラス化、混合、実装はできません。
  • 列挙型を明示的にインスタンス化することはできません。

詳細は、Dart language specification を参照してください。

クラスに機能を追加: Mixins

ミックスインは、複数のクラス階層でクラスのコードを再利用する方法です。

ミックスインを使用するには、withキーワードの後に 1つ以上のミックスイン名を続けて使用します。次の例は、ミックスインを使用する 2つのクラスを示しています。

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

ミックスインを実装するには、Object を継承し、コンストラクタを宣言しないクラスを作成します。mixinを通常のクラスとして使用したい場合を除き、classの代わりにmixinキーワードを使用します。例えば、以下のようになります。

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

ミックスインを使用できる型を制限したい場合もあるでしょう。例えば、ミックスインが定義されていないメソッドを呼び出すことができるかどうかに依存している場合があります。次の例では、必要なスーパークラスを指定するためにonキーワードを使用することで、ミックスインの使用を制限することができます。

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

前述のコードでは、Musicianクラスを継承しているか実装しているクラスだけがMusicalPerformerをミックスインできます。SingerDancerMusicianを継承しているので、MusicalPerformerをミックスすることができます。

クラス変数とクラスメソッド

クラス全体の変数やメソッドを実装するには、staticキーワードを使用します。

静的変数

静的変数(クラス変数)は、クラス全体の状態や定数を把握するのに便利です。

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

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

静的変数は使用されるまで初期化されません。

Note: このページでは、style guide recommendation されている定数名はlowerCamelCaseを使用するようにしています。

スタティックメソッド

静的メソッド(クラスメソッド)はインスタンス上では動作しないので、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);
}

Note: 一般的に使用されている、または広く使用されているユーティリティや機能については、静的メソッドではなくトップレベルの関数を使用することを検討してください。

静的メソッドをコンパイル時の定数として使用することができます。たとえば、定数コンストラクタのパラメータとして静的メソッドを渡すことができます。

ジェネリクス

基本的な配列の型であるListのAPIドキュメントを見ると、実際にはList<E>であることがわかります。<...>記法は、Listを一般的な(またはパラメータ化された)型、つまり形式的な型パラメータを持つ型であることを示しています。慣習的に、ほとんどの型変数は、E, T, S, K, V のような一文字の名前を持ちます。

なぜジェネリックを使うのか?

ジェネリックは型の安全性を確保するために必要とされることが多いですが、単にコードを実行できるようにするだけではなく、それ以上の利点があります。

  • ジェネリック型を適切に指定することで、より良いコードが生成されます。
  • ジェネリックを使用してコードの重複を減らすことができます。

文字列のみを含むリストを作成したい場合は、List<String>として宣言することができます。そうすれば、あなたや他のプログラマー、そしてツールは、リストに文字列以外のものを代入することが間違いであることを検出することができます。以下に例を示します。

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

ジェネリックを使用するもう一つの理由は、コードの重複を減らすことです。ジェネリックを使用すると、静的解析の利点を生かしつつ、多くのタイプの間で単一のインターフェイスと実装を共有することができます。例えば、オブジェクトをキャッシュするためのインターフェイスを作成したとします。

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

このインターフェイスの文字列固有のバージョンが必要であることがわかったので、別のインターフェイスを作成します。

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

後になって、このインターフェイスの番号固有のバージョンが欲しいと思ったら...。

汎用型を使用することで、これらすべてのインターフェースを作成する手間を省くことができます。代わりに、型パラメータを取る単一のインタフェースを作成できます。

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

このコードでは、Tはスタンドイン型です。これはプレースホルダで、開発者が後で定義する型と考えることができます。

コレクションリテラルの使用

リスト、セット、およびマップ・リテラルは、パラメータ化することができます。パラメータ化されたリテラルは、<type> (リストとセットの場合) または<keyType, valueType> (マップの場合) を開括弧の前に追加することを除いては、すでに見たリテラルと同じです。ここでは、型付きリテラルの使用例を示します。

var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};

パラメータ化された型をコンストラクタで使う

コンストラクタを使用する際に 1つ以上の型を指定するには、クラス名の直後に角括弧 (<...>) を付けます。例えば、以下のようになります。

var nameSet = Set<String>.from(names);

次のコードは、View 型の整数キーと値を持つマップを作成します。

var views = Map<int, View>();

ジェネリックコレクションとそれに含まれる型

Dartの汎用型は再定義されており、実行時に型情報を持ち歩くことができます。例えば、コレクションの型をテストすることができます。

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

Note: 対照的に、Javaのジェネリクスは消去を使用します。これは、実行時にジェネリック型のパラメータが削除されることを意味します。Javaでは、オブジェクトがListであるかどうかはテストできますが、List<String>であるかどうかはテストできません。

パラメータ化された型の制限

ジェネリック型を実装する際に、パラメータの型を制限したい場合があります。これにはextendsを使用します。

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

一般的な引数としてSomeBaseClassやそのサブクラスを使用しても構いません。

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

汎用引数を指定しないのもOKです。

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'

SomeBaseClass型を指定するとエラーになります。

var foo = Foo<Object>();

ジェネリックメソッドの使用

当初、Dartのジェネリックサポートはクラスに限定されていました。ジェネリックメソッドと呼ばれる新しい構文では、メソッドや関数の型引数を許可しています。

T first<T>(List<T> ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}

ここでは、first(<T>)の一般的な型パラメータによって、いくつかの場所で型引数Tを使用することができます。

  • 関数の戻り値の型 (T)
  • 引数の型 (List<T>)
  • ローカル変数の型 (T tmp)

ライブラリと可視性

importディレクティブとlibraryディレクティブは、モジュール化された共有可能なコードベースを作成するのに役立ちます。ライブラリはAPIを提供するだけでなく、プライバシーの単位でもあります。アンダースコア(_)で始まる識別子はライブラリ内でのみ表示されます。すべてのDartアプリは、たとえlibraryディレクティブを使用していなくても、ライブラリです。

ライブラリは packages を使って配布することができます。

ライブラリの使用

あるライブラリの名前空間が別のライブラリのスコープでどのように使用されるかを指定するには importを使用します。

例えば、Dartウェブアプリは一般的にdart:htmlライブラリを使用しており、このようにインポートすることができます。

import 'dart:html';

importに必要な唯一の引数は、ライブラリを指定するURIです。組み込みライブラリの場合、URIは特別なdart:スキームを持ちます。その他のライブラリについては、ファイルシステムパスまたはpackage:スキームを使用することができます。package:スキームは、pubツールなどのパッケージマネージャが提供するライブラリを指定します。例えば、以下のようになります。

import 'package:test/test.dart';

Note: URIはuniform resource identifierの略です。URL (uniform resource locators) は一般的なURIの一種です。

ライブラリのプレフィックスを指定する

識別子が異なる 2つのライブラリをインポートする場合、片方または両方のライブラリにプレフィックスを指定することができます。例えば、ライブラリ1とライブラリ2の両方がElementクラスを持っている場合、次のようなコードになります。

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();

ライブラリの一部のみをインポートする

ライブラリの一部のみを使用したい場合は、ライブラリを選択的にインポートすることができます。例えば、以下のようになります。

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

ライブラリの遅延ロード

Deferred loading (lazy loadingとも呼ばれます)は、ライブラリが必要なときと場合に、Webアプリがオンデマンドでライブラリを読み込むことを可能にします。ここでは、遅延ローディングを使用するいくつかのケースを紹介します。

  • ウェブアプリの初期起動時間を短縮する。
  • 例えば、アルゴリズムの代替実装を試すなど、A/Bテストを実行する。
  • オプションの画面やダイアログなど、めったに使われない機能をロードする。

dart2js だけが遅延ロードをサポートしています。Flutter、Dart VM、dartdevcは遅延ロードをサポートしていません。詳細は issue #33118issue #27776 を参照してください。

ライブラリを遅延ロードするには、まずdeferred asを使ってライブラリをインポートする必要があります。

import 'package:greetings/hello.dart' deferred as hello;

ライブラリが必要な場合は、ライブラリの識別子を使ってloadLibrary()を呼び出してください。

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

前述のコードでは、awaitキーワードは、ライブラリがロードされるまで実行を一時停止します。asyncおよびawaitの詳細は、非同期サポート を参照してください。

ライブラリ上でloadLibrary()を複数回呼び出しても問題ありません。ライブラリがロードされるのは一度だけです。

deferred loading を使用する際には、以下の点に注意してください。

  • deferredライブラリの定数は、インポートファイル内の定数ではありません。これらの定数は、deferredライブラリがロードされるまで存在しないことを覚えておいてください。
  • インポートファイルでは、deferredライブラリの型を使用することはできません。代わりに、インターフェイスの型を、deferredライブラリとインポートファイルの両方でインポートされたライブラリに移動することを検討してください。
  • Dartは、deferred as namespaceを使用して定義した名前空間にloadLibrary()を暗黙的に挿入します。loadLibrary()関数はFutureを返します。

ライブラリの実装

以下を含む、ライブラリパッケージの実装方法についてのアドバイスは、Create Library Packages を参照してください。

  • ライブラリのソースコードを整理する方法。
  • exportディレクティブの使い方。
  • partディレクティブを使うタイミング。
  • libraryディレクティブを使用するタイミング
  • 複数のプラットフォームをサポートするライブラリを実装するために条件付きインポートとエクスポートを使用する方法。

非同期サポート

Dartライブラリには、FutureStreamオブジェクトを返す関数がたくさんあります。これらの関数は非同期で、時間のかかる操作(I/Oなど)を設定した後、その操作が完了するのを待たずに戻ります。

asyncawaitキーワードは非同期プログラミングをサポートしており、同期コードに似た非同期コードを書くことができます。

Futureの取り扱い

完了したFutureの結果が必要な場合、2つのオプションがあります。

  • asyncawaitを使用します。
  • in the library tour で説明されているように、Future APIを使用します。

asyncawaitを使うコードは非同期ですが、同期コードによく似ています。例えば、非同期関数の結果を待つためにawaitを使用するコードを以下に示します。

await lookUpVersion();

awaitを使用するには、コードがasync関数(asyncとしてマークされた関数)に含まれている必要があります。

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

Note: async関数は時間のかかる処理を実行するかもしれませんが、それらの処理を待つことはありません。その代わりに、async関数は最初のawait式 (details) に遭遇するまでのみ実行します。その後、Futureオブジェクトを返し、await式が完了した後にのみ実行を再開します。

trycatchfinallyを使用して、awaitを使用するコードのエラーやクリーンアップを処理します。

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}

async関数では、複数回awaitを使用することができます。例えば、以下のコードは関数の結果を3回待ちます。

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await式では、の値は通常Futureであり、そうでない場合は自動的にFutureに包まれます。このFutureオブジェクトは、オブジェクトを返す約束を示しています。await式の値は、返されたオブジェクトです。await式は、そのオブジェクトが利用可能になるまで実行を一時停止します。

awaitを使用しているときにコンパイル時エラーが発生した場合は、awaitasync関数の中にあることを確認してください。例えば、アプリのmain()関数でawaitを使用するには、main()の本体をasyncとマークする必要があります。

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

async関数の宣言

async関数とは、本体にasync修飾子が付いている関数のことです。

関数にasyncキーワードを追加すると、Futureを返すようになります。たとえば、次のような文字列を返す同期関数を考えてみましょう。

String lookUpVersion() => '1.0.0';

これをasync関数に変更した場合 (たとえば、将来の実装に時間がかかるため)、返される値はFutureになります。

Future<String> lookUpVersion() async => '1.0.0';

関数の本体は Future API を使用する必要がないことに注意してください。Dart は必要に応じてFutureオブジェクトを作成します。関数が有用な値を返さない場合は、その戻り値の型をFuture<void>にします。

futures、asyncawaitの使い方については、asynchronous programming codelab を参照してください。

Streamの取り扱い

ストリームから値を取得する必要がある場合、2つのオプションがあります。

  • asyncと非同期のforループ(await for)を使う。
  • in the library tour で説明されているように、Stream APIを使用します。

Note: await forを使う前に、コードがより明確になること、そして本当にストリームの結果をすべて待ちたいのかどうかを確認してください。例えば、UIのイベントリスナーには通常await forは使用すべきではありません。

非同期の for ループは次のような形式になります。

await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}

式の値はStream型でなければなりません。実行は以下のように進みます。

  1. ストリームが値を出すまで待ちます。
  2. ストリームが値を出力するまで待機します。
  3. ストリームが終了するまで 1 と 2 を繰り返します。

ストリームのlisteningを停止するには、breakまたはreturnを使用して、forループから抜け出し、ストリームからのサブスクライブを解除します。

非同期forループの実装時にコンパイル時エラーが発生した場合は、await forasync関数内にあることを確認してください。例えば、アプリのmain()関数で非同期のforループを使用するには、main()の本体をasyncとマークする必要があります。

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

一般的な非同期プログラミングの詳細については、ライブラリツアーの dart:async のセクションを参照してください。

ジェネレーター

一連の値を簡単に生成する必要がある場合は、ジェネレータ関数の使用を検討してください。Dartは2種類のジェネレータ関数をビルトインでサポートしています。

  • 同期ジェネレータ: Iterable オブジェクトを返します。
  • 非同期ジェネレータ: Stream オブジェクトを返します。ストリームオブジェクトを返します。

同期ジェネレータ関数を実装するには、関数本体をsync*としてマークし、yieldを使用して値を出力します。

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

非同期ジェネレータ関数を実装するには、関数本体をasync*でマークし、yieldを使用して値を配信します。

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

呼び出し可能クラス

Dart クラスのインスタンスを関数のように呼び出すには、call()メソッドを実装します。

次の例では、WannabeFunctionクラスが call() 関数を定義しており、3つの文字列を取り、それらを連結し、それぞれをスペースで区切り、感嘆符を付けます。Dart pad等で以下のコードを実行してください。

class WannabeFunction {
  String call(String a, String b, String c) => '$a $b $c!';
}

var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');

main() => print(out);

Isolates

ほとんどのコンピュータは、モバイルプラットフォームであっても、マルチコアCPUを搭載しています。これらすべてのコアを利用するために、開発者は伝統的に同時実行の共有メモリスレッドを使用しています。しかし、共有状態での同時実行はエラーが発生しやすく、複雑なコードになる可能性があります。

スレッドの代わりに、すべてのDartコードはアイソレートの中で実行されます。各アイソレートは独自のメモリヒープを持ち、アイソレートの状態が他のアイソレートからアクセスできないようにします。

詳細については、以下を参照してください。

Typedefs

Dartでは、文字列や数値がオブジェクトであるように、関数はオブジェクトです。typedef(関数型のエイリアス)は、フィールドや戻り値の型を宣言する際に使用できる名前を関数型に与えます。型定義は、関数型が変数に代入されたときに型情報を保持します。

以下のコードでは、型定義を使用していません。

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}

fcompareに代入すると型情報が失われます。fの型は(Object, Object)int(ここで → は戻り値を意味します) であるにもかかわらず、compareの型はFunctionです。明示的な名前を使い、型情報を保持するようにコードを変更すれば、開発者もツールもその情報を利用できるようになります。

typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

Note: 現在、型定義は関数型に限定されています。これは変更されることを期待しています。

型定義は単なるエイリアスなので、任意の関数の型をチェックする方法を提供します。例えば、以下のようになります。

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!
}

Metadata

メタデータを使用して、コードに関する追加情報を提供します。メタデータアノテーションは文字@で始まり、その後にコンパイル時定数(deprecatedなど)への参照、または定数コンストラクタへの呼び出しが続きます。

2つのアノテーションはすべての Dartコードで利用できます。@deprecated@overrideです。オーバーライドの使用例については、クラスの拡張 を参照してください。ここでは、@deprecatedアノテーションを使用した例を示します。

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {...}
}

独自のメタデータアノテーションを定義することができます。ここでは、2つの引数を取る@todoアノテーションを定義した例を示します。

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

そして、その@todoアノテーションを使った例がこちらです。

import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

メタデータは、ライブラリ、クラス、型定義、型パラメータ、コンストラクタ、ファクトリ、関数、フィールド、パラメータ、変数の宣言の前や、インポートやエクスポート指示文の前に現れることがあります。リフレクションを使用して、実行時にメタデータを取得することができます。

コメント

Dartは、単一行コメント、複数行コメント、ドキュメントコメントをサポートしています。

シングルラインコメント

シングルラインコメントは//で始まります。//から行末まではすべて Dartコンパイラによって無視されます。

void main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

マルチラインコメント

複数行のコメントは/*で始まり*/で終わります。/**/の間はすべて Dart コンパイラによって無視されます (コメントがドキュメントコメントでない限り、次のセクションを参照してください)。複数行コメントはネストすることができます。

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

ドキュメンテーションコメント

ドキュメントコメントは、///または/**で始まる複数行または1行のコメントです。連続した行に ///を使用すると、複数行のドキュメントコメントと同じ効果があります。

ドキュメント・コメントの内部では、括弧で囲まれていない限り、Dart コンパイラはすべてのテキストを無視します。括弧を使用すると、クラス、メソッド、フィールド、トップレベル変数、関数、およびパラメータを参照できます。括弧内の名前は、ドキュメント化されたプログラム要素の語彙スコープで解決されます。

ここでは、他のクラスや引数を参照したドキュメントコメントの例を示します。

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

生成されたドキュメントでは、[Food]はFoodクラスのAPIドキュメントへのリンクになります。

Dartコードを解析してHTMLドキュメントを生成するには、SDKの documentation generation tool を使用します。生成されるドキュメントの例については、Dart API documentation を参照してください。コメントの構造についてのアドバイスは、Guidelines for Dart Doc Comments を参照してください。

サマリー

このページでは、Dart言語でよく使われる機能をまとめました。より多くの機能が実装されていますが、既存のコードを壊すことはないと思われます。詳細については、Dart language specificationEffective Dart を参照してください。

Dartのコアライブラリの詳細については、A Tour of the Dart Libraries を参照してください。

Sound null safety

Sound null safetyに関する記事は、beta版の内容となります。

現在ベータ版のSound null safetyがDart言語に登場します。

null safetyを選択すると、コード内の型はデフォルトでnull不可になります。null safetyを使用すると、ランタイムのnull参照エラーは、編集時の解析エラーに変わります。

通常の開発環境でnull safetyを試したり、既存のコードを移行してnull safetyを使用したり、次のスクリーンショットに示すように、WebアプリのDartPad with Null Safetyでnull safetyを使用する練習をすることができます。

Null safetyの原則

Dartのnull safetyサポートは、以下の3つのコア設計原則に基づいています。

  • デフォルトではnull不可。 Dartに明示的に変数がnullになるように指示しない限り、変数はnull不可とみなされます。このデフォルトは、APIではnone-nullが最も一般的な選択であることが調査で判明したために選択されました。
  • 段階的に採用可能。 何をいつ、何をnull safetyに移行するかを選択します。同じプロジェクト内でnull-safeと非null-safeのコードを混在させながら、段階的に移行することができます。移行を支援するツールを提供します。
  • Fully sound. Dartのnull safetyは健全であり、コンパイラの最適化を可能にします。型システムが何かがnone-nullと判断した場合、その何かがnullになることはありません。プロジェクト全体とその依存関係をnull safetyに移行すると、バグが減るだけでなく、バイナリのサイズが小さくなり、実行が速くなるなど、健全性のメリットをフルに享受することができます。

Null safety機能のツアー

null safetyに関連する新しい演算子やキーワードには?!lateがあります。Kotlin、TypeScript、C#を使ったことがある人なら、null safetyの構文は見覚えがあるかもしれません。これは意図したもので、Dart言語はサプライズが少なくなることを目指しています。

変数の作成

変数を作成する際には、?lateを使用して変数のnull可能性をDartに通知することができます。

none-null変数を宣言する例を以下に示します(null safetyを選択していると仮定しています)。

// In null-safe Dart, none of these can ever be null.
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo();

変数の値がnullになる可能性がある場合は、その型宣言に?を追加します。

int? aNullableInt = null;

none-null変数が使用される前にnull以外の値に初期化されることがわかっていても、Dartアナライザーがエラーを出す場合は、変数の型の前にlateを追加します。

class IntProvider {
  late int aRealInt;

  IntProvider() {
    aRealInt = calculate();
  }
}

lateキーワードには、2つの効果があります。

  • アナライザは、直ちにlate変数をnone-null値に初期化する必要はありません。
  • ランタイムはlate変数を遅延的に初期化します。例えば、none-null値であるインスタンス変数を計算する必要がある場合、late修飾子を追加すると、インスタンス変数が最初に使用されるまで計算が遅れます。

変数と式の使用

null safety機能を使用すると、Dartアナライザは、none-null値が必要な場所でnull可能な値を検出するとエラーを生成します。これは、聞こえるほど悪いことではありません: アナライザは、関数内の変数や式がnull可能な型を持っていても、null値を持つことができない場合に認識できることがよくあります。

アナライザはアプリケーション全体のフローをモデル化できないので、グローバル変数やクラスフィールドの値を予測することはできません。

null可能な変数や式を使用する場合は、必ずnull値を処理してください。例えば、if文、??演算子、または?.演算子を使用して、可能性のあるnull値を処理することができます。

??演算子を使用して、null値でない変数をヌルに設定しないようにする例を示します。

int value = aNullableInt ?? 0; // 0 if it's null; otherwise, the integer

以下に似たようなコードですが、nullをチェックするためにif文を使用しています。

int definitelyInt(int? aNullableInt) {
  if (aNullableInt == null) {
    return 0;
  }
  return aNullableInt; // Can't be null!
}

null可能な型を持つ式がnullではないと確信している場合は、!を追加してDartがそれをnullではないものとして扱うようにすることができます。

int? aNullableInt = 2;
int value = aNullableInt!; // `aNullableInt!` is an int.
// This throws if aNullableInt is null.

重要: 値がnone-nullであることを確認できない場合は、!演算子を使用しないでください。

null可能な変数の型を変更する必要がある場合、!演算子でできることを超えて、typecast演算子(as)を使用することができます。次の例では、asを使用してnum?intに変換しています。

return maybeNum() as int;

null safetyを選択すると、オペランドがnullである可能性がある場合は、メンバ・アクセス演算子(.)を使用できなくなります。その代わりに、null対応バージョンの演算子(?.)使用できます。

double? d;
print(d?.floor()); // Uses `?.` instead of `.` to invoke `floor()`.

リスト、セット、マップの種類を理解する

リスト、セット、およびマップはDartプログラムで一般的に使用されるコレクション型なので、それらがnull safetyとどのように相互作用するかを知っておく必要があります。ここでは、Dartコードがこれらのコレクション型をどのように使用しているかの例をいくつか紹介します。

  • ColumnなどのFlutter layout widgetsには、WidgetオブジェクトのListであるchildrenプロパティがあることがよくあります。
  • Veggie Seasonsのサンプルでは、VeggieCategorySetを使用して、ユーザーの好みの食べ物を保存しています。
  • GitHub Datavizのサンプルには、Map<String, dynamic>で提供されるJSONデータからオブジェクトを作成するfromJson()メソッドがあります。
リスト型とセット型

リストやセットの型を宣言する際には、何がnullになり得るかを考えてみましょう。次の表は、null safetyを選択した場合の文字列リストの可能性を示しています。

Type リストをnullに出来るか null要素を使えるか 詳細
List<String> No No none-null文字列 で none-nullリスト
List<String>? Yes No none-null文字列 で リストはnull可能性あり
List<String?> No Yes 文字列はnull可能性あり で none-nullリスト
List<String?>? Yes Yes 文字列はnull可能性あり で リストはnull可能性あり

リテラルがリストやセットを作成する場合、上の表のような型の代わりに、通常はリテラルに型のアノテーションが表示されます。例えば、List<String?>型の変数 (nameList) と Set<String?>型の変数 (nameSet) を作成するために使用するコードを以下に示します。

var nameList = <String?>['Andrew', 'Anjan', 'Anya'];
var nameSet = <String?>{'Andrew', 'Anjan', 'Anya'};
マップ型

マップ型は、ほとんどの場合、期待される動作をしますが、1つの例外があります。key指定で値を取得した場合は、戻り値がnullになる可能性があります。nullは、マップに存在しないキーの値です。

例として、次のコードを見てください。uhOhの値と型は何だと思いますか?

var myMap = <String, int>{'one': 1};
var uhOh = myMap['two'];

答えはuhOhは nullで、型はint?です。

リストやセットと同様に、マップにも様々な型を持つことができます。

Type マップをnullに出来るか null要素を使えるか
Map<String, int> No No ※
Map<String, int>? Yes No ※
Map<String, int?> No Yes
Map<String, int?>? Yes Yes

※マップ内のすべての int値がnullでない場合でも、無効なキーを使用してマップ検索を実行すると、返される値はnullになります。

マップ検索はnullを返す可能性があるため、null以外の変数には代入できません。

// Assigning a lookup result to a non-nullable
// variable causes an error.
int value = <String, int>{'one': 1}['one']; // ERROR

回避策としては、変数の型をnull可能なものに変更することです。

int? value = <String, int>{'one': 1}['one']; // OK

問題を解決するもう一つの方法は、値の取得が成功すると確信している場合は、!を追加することです。

int value = <String, int>{'one': 1}['one']!; // OK

より安全な方法は、nullでない場合にのみ取得した値を使用することです。if文や??演算子を使って値をテストすることができます。ここでは、値の取得時でnull値を返した場合に値0を使用する例を示します。

var aMap = <String, int>{'one': 1};
...
int value = aMap['one'] ?? 0;

Null safetyを有効にする

null safetyは現在ベータ機能です。Dartまたは Flutter SDKの最新のbeta channel releaseを必要とし、それを使用することをお勧めします。最新のリリースを検索するには、Dart SDK アーカイブのbeta channelセクション、またはFlutter SDK archiveのbeta channelセクションを参照してください。

null safetyサポートを持つlanguage versionを要求するように SDK constraintsを設定します。例えば、pubspec.yamlファイルには以下のような制約があるかもしれません。

environment:
  sdk: ">=2.12.0-0 <3.0.0"

Note: 上記の2.12 SDK制約は-0で終わっています。 このようにsemantic versioning表記を使用することで、2.12.0のプリリリース、例えば2.12.0-29.10.betaのプリリリースが可能になります。

もしnull safetyの問題を見つけた場合は、feedbackをお願いします。

20
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
19