Dartのnullの扱い方を何度も調べている気がするので、まとめてみたいと思います。
公式ドキュメントから抜き出し・意訳・追記しています。(訳に誤りがありましたら申し訳ありません。)
Dartのnullのコンセプト
・堅固なnull Safetyにするために、Dartの変数は、デフォルトで ‘non-nullable’ になっています。この時、変数には、宣言された型の値のみをセットでき、nullはセットできません。 (e.g. int i=42)
・変数をnullableであると明示した時だけ、nullと宣言された型の値の両方をセットできるようになります。(e.g. int? i)
・堅固なnull Safetyによって、ランタイムエラーを引き起こす可能性のある箇所を、コーディング時に気がつくことができるエラーに変えます。このおかげで、アプリをデプロイする前に、これらのエラーを修正しておくことができます。
(「堅固なnull Safety」は、原文では、"Sound null Safety" となっています。"Sound" は形容詞で、「健全な、堅固な」という意味があるんですね。知りませんでした。)
復習
なんと公式ドキュメントに、解説と練習問題のページがあります。なんて親切なのでしょう。
? → null許容型であることを明示する
型の後ろに「?」をつけると、null許容型(nullable)になります。逆にいうと、「?」がついていない時は、nullは入れられません。
Listの時は、以下のような扱いになるようです。
//List自体がnullable
List<String>? aNullableListOfStrings;
//Listの要素がnullable
List<String?> aListOfNullableStrings = ['one', null, 'three'];
! → null許容型を‘non-nullable’と同じ扱いにする
nullableな型で、nullでないのが確かな場合、変数や関数の後ろに!をつけることで、‘non-nullable’と同じように扱うことができます。
※nullになる可能性のあるところで「!」をつけてしまうと、ランタイムエラーを生んでしまうので、nullでないことを "you are very sure" な時だけ使ってください。とのこと。
例(変数の場合):
List<int?> listThatCouldHoldNulls = [2, null, 4];
//一つ目の要素はnullでないので、「!」をつけてintに代入する(つけないとコンパイルエラーになる)
int b = listThatCouldHoldNulls.first!; // first item in the list
例(関数の場合):
int? couldReturnNullButDoesnt() => -3;
//nullは返さないので、「!」をつけて、その後に関数を使える
int c = couldReturnNullButDoesnt()!.abs(); // absolute value
?. → nullではない時だけ実行する
nullが入っているかもしれない値を扱う時に、「?.」を使うと、その後の式は、nullでない場合だけに実行されます。
例:
//"nullableString" がnullでない時は、lengthの値を返す。nullの場合はnullを返す
nullableString?.length;
?? → nullだったら違う値をセットする
例:
///notnullValueがnullでない場合に、notnullValueを、nullの場合nullValueをセットする
String value = notnullValue ?? nullValue;
type promotion → nullでないことの自動判定
以下のような場合、Dartはnullableの変数の値がnullでないことを検知して、‘non-nullable’の型と同じように扱えるようになります。これを、"type promotion" といいます。
・利用箇所より上で、値が必ずセットされている場合
・nullチェックがされていて、nullの場合にreturnやthrow Exceptionになっている場合
例:
int getLength(String? str) {
// nullの場合は0を返す
if(str==null){
return 0;
}
//本来 "str" はnullableなので、.lengthをつけたらコンパイルエラーになるが、
//上でnullチェックしてreturnしていることにより、エラーにならない。
return str.length;
}
late → 後で値を設定する
変数宣言の前に「late」をつけることで、Dartに以下のことを伝えます。
・まだ値をセットしていない。
・後で値をセットする予定
・変数が使われる前に、値をセットすることを確信している
late で宣言した変数を使う前に値をセットしていないと、エラーが発生します。
※メモ:ただ、試したみたところ、このエラーは、コンパイルエラーではなく実行時エラーのようでした。
例:
class Meal {
//lateで宣言
late String _description;
set description(String desc) {
_description = 'Meal description: $desc';
}
String get description => _description;
}
void main() {
final myMeal = Meal();
myMeal.description = 'Feijoada!';//この行をコメントアウトすると、実行時エラー
print(myMeal.description);
}
特別な使い方として、初期値が設定されている変数に「late」をつけると、その変数が使われる時に、初めて初期値が設定されます。初期値設定のコストが高く、必ずしも必要にならない時に便利です。
例:
class CachedValueProvider {
late final _cache = _computeValue();//初期値設定しているが、lateをつけている。
int get value => _cache;
}
void main() {
print('Calling constructor...');
var provider = CachedValueProvider();
print('Getting value...');
print('The value is ${provider.value}!'); //ここで初めて初期値の設定がよばれる
}
感想
曖昧な理解のままに、場当たり的に?をつけたりしてしまっていましたが、ようやくすっきり理解できた気がします。Dartさんのnull Safetyに対するこだわりも伝わってきました。Dartの公式ドキュメントはとても親切であることがわかったので、もっと活用していきたいです。
情報源