前のページ

言語編

次のページ


変数とその型(Variables and Types)

変数はメモリ内の蓄積場所である。

変数にはトップ・レベルに宣言された変数(トップ・レベル変数)、あるクラスに結び付けられた変数(static変数)、及びあるオブジェクトに結び付けられた変数(インスタンス変数)がある。インスタンス変数はあるオブジェクトのフィールド、あるいは属性(property)とも呼ばれる。

非ヌル制約型とそれのデフォルト化の提案に関して

2015527日のDart言語幹部たちのDart言語強化提案(DEP)会合で議論された提案である。これはPatrice Chalin氏の提案によるものである。この提案は非ヌル制約型とそれのデフォルト化(Non-null Types and Non-null By Default (NNBD))である。非ヌル制約型によりnullの変数を参照するエラー(いわゆるNPE : null pointer error)を排除し、またJSに変換した際のオーバヘッドを低減できる。これは2.0版として導入される可能性があるとされてきた。しかしながら2.0になっても導入されることはなかったこれは余りにもインパクトが大きかったこともその原因だったと推察される。



変数(Variables)

以下はある変数の生成とそれの初期化の例である:

var name = 'Bob';

変数は参照をストアする。即ち参照渡しである。nameと呼ばれるこの変数は“Bob”という値を持ったあるString型のオブジェクトへの参照を保持している。

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

dynamic name = 'Bob';

別の選択肢としては、それが型推論されるであろう型を明示的に宣言する方法がある:

String name = 'Bob';

注意:このページではローカル変数に対しては型アノテーションではなくてvarを使うというスタイル・ガイドの勧告に準拠している。



デフォルト値 (Default value)

初期化されていない変数はその初期値としてnullを持つ。たとえ数値型の変数であっても初期値はnullである。何故ならDart内のどこにおいても総てがオブジェクトであるように数値もオブジェクトだからである。

int lineCount;
assert(lineCount == null);

注意:assert()呼び出しは運用コード(production code)では無視される。開発段階ではassert(condition)はそのconditiontrueでない限り例外を生起する。



finalconst

ある変数の値を決して変わらないようにしたいときは、varを使ったり型アノテーションを付加する代わりにfinalまたはconstを使用する。final変数はただ一回セットされる;const変数はコンパイル時常数である。(const変数は暗示的なfinalである)finalなトップ・レベル変数又はクラス変数は、それが最初に使用されるときにおいて初期化される。これをGoogleは後まわしの初期化(lazy initialization)と呼んでいる。プログラムの実行中に初めて生じる初期化のために処理がそこで遅延を起こす可能性が出る。これは処理時間にクリチカルなアプリケーションでは注意が必要である。しかしGoogleにとってクライアント側(ブラウザ上)でのアプリケーションの起動が早いことはそれよりも優先される。Googleの最大の顧客は一般ユーザであって、プログラマではない。

注意:インスタンス変数はfinalであり得るがconstにはなり得ない。finalなインスタンス変数はそのコンストラクタのボディが開始する前…その変数宣言の前に、コンストラクタ・パラメタ、またはそのコンストラクタの初期化リストのなかで、初期化されていなければならない。

以下はfinalな変数の生成と設定の例である:

final name = 'Bob'; // 型アノテーションなし
final String nickname = 'Bobby';

final変数の値を変更することはできない。例えばfinalな変数に'Alice'という値をセットしようとすると以下のエラーがだされる:

final name = 'Bob'; // 型アノテーションなし
final String nickname = 'Bobby';
name = 'Alice'; // Error: a final variable can only be set once.

コンパイル時常数としたいときはその変数には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をオミットできる

例えそれがconst値を使っていたとしても、非final、非constな変数の値を変えることができる:

foo = [1, 2, 3]; // const []だった

constfinal変数の値を変更することはできない:

bar = [42]; // Error: 'bar', a final variable, can only be set once.
baz = [42]; // Error: Constant variables can't be assigned a value.

finalMapでは変更は可能である:

final a = {'x': 1};
main() {
  print('final a = $a');
  a['y'] = 2;
  print('manipulated a = $a');
  a['x'] = 3;
  print("manipulated a['x'] = $a");
}
/*
final a = {x: 1}
manipulated a = {x: 1, y: 2}
manipulated a['x'] = {x: 3, y: 2}
*/

finalconstに変更すると4行目でエラーとなる:

Unsupported operation: Cannot set value in unmodifiable Map

常数値生成にconstを使用することに関しては「組込み型の章」のListMap,及び「Classの章」を参照のこと。



代入に関する注意事項 (Assignment)

Javaでは値渡しとなるプリミティブは存在せず、Dartでは総てがオブジェクトである。Java同様、Dartでは代入の右辺を計算した結果のオブジェクトが左辺にバインドされる。即ち参照渡しであることに注意されたい。2つの変数間のa=b;という代入はひとつのオブジェクト参照(バインド)していることになる。Stringnumのような単純なオブジェクトであればその後abに代入を行えばそこで両者間の参照関係が切れてしまうので、通常は気にする必要はない。しかしオブジェクトabそのものに別のオブジェクトを代入をしない限り、バインド即ち参照は維持される。これはオブジェクトbをラップしたクラスなどに良く使われる。

そのような例として良く使われるのがListMap、及びクラスのようなオブジェクトである。次の例を考えてみよう:

Map original = {};
main(){
  original["a"]=3.14;
  original["b"]=1.414;
  print("original : $original");
  Map copy = original;
  copy["c"] = "Hello copy!";
  print("copy : $copy");
  print("original : $original");
}
/*
original : {a: 3.14, b: 1.414}
copy : {a: 3.14, b: 1.414, c: Hello copy!}
original : {a: 3.14, b: 1.414, c: Hello copy!}
 */

この場合はcopyのオブジェクトはその要素を変更してもoriginalとの参照が維持される。copy["c"] = "Hello copy!";という操作は代入ではなくて追加である。従ってコピーしたほうのcopyにそのような変更を加えれば、それはoriginalにも変更を加えたことになる。

同じことがクラスにおいても言える:

class Original {
  var a = 1;
  var b;
}
main() {
  var original = Original();
  var copy = original;
  copy.b = 2;
  print('original.a = ${original.a}');
  print('original.b = ${original.b}');
}
/*
original.a = 1
original.b = 2
 */

copy.b = 2;という操作はcopyというオブジェクトに新たなオブジェクトを代入している訳ではないので、copyoriginalとの参照を維持し続ける。

上記2つの例でcopyという変数がoriginalとの参照を必ず維持し続ける為にはvar copy Map copyという記述ではなくて、final copy (あるいはfinal Map copy)と記述するよう習慣づけるべきである。finalな変数は初期化時にそのバインディングが固定される変数である。従ってfinalな変数は初期化後は常に同じオブジェクトを参照する

もうひとつ勘違いしそうな例を示そう:

void reassignOne(List arg) {
  arg = List.from([100, 200, 300]);
  print('In call: $arg');
}
void main() {
  List list = [1, 2, 3];
  print(list);
  reassignOne(list);
  print(list);
}
/*
[1, 2, 3]
In call: [100, 200, 300]
[1, 2, 3]
*/

この場合はreassignOneという関数の引数argにはlist即ち[1, 2, 3]オブジェクトへの参照が渡される。しかしながらこの関数の中ではargにはList.from([100, 200, 300])という新しいオブジェクトへの参照が渡される。すなわちここで外のlistとの参照関係が無くなってしまう。従ってreassignOnelistの中身を変更することにはならない。







前のページ

次のページ