Null safety codelabやってみた
FlutterをやっていてDartのNull safetyがいまいち分からなかったので、公式が出しているcodelabをやってみました。こちらのcodelab、問題はあるものの解答らしきものはなかったので、自分の解答も合わせて書いておきます。
間違いなどありましたら、ご連絡いただければさいわいです!
今回やったもの
null-safe type systemについて
- 導入: Dart 2.12
null safetyを導入すると明示的に指定しない限り、コード中で変数にnull
を入れることができないようにできる。
コメント: ぬるぽとおさらばできるのはありがたいですね。
このcodelabで学べること
- Nullable型とnon-nullable型について
- nullを許容するかどうかを示す
?
や!
をいつ使うか - 構文解析とtype promotionのフロー
-
late
演算子が変数と初期化に与える影響
コメント: type promotionってなに?
Nullable型とnon-nullable型について
null safety導入後はすべての型でnon-nullableがデフォルトになり、以下のa
にnull
を代入しようとするとコンパイルエラーになります。
void main() {
int a;
a = null; // Null can't be assigined
print('a is $a.');
}
※コード内コメントは筆者が追記。
null
を許容したい場合は?
を使って明示する必要があります。
- 解答
void main() {
int? a;
a = null;
print('a is $a.'); // a is null
}
Nullable type parameters for generics
ジェネリクスの型パラメータも同じくnull
を許容するかどうかを指定できます(デフォルトはnon-nullable)
Exercise: Nullable type parameters for generics
- 解答
void main() {
List<String> aListOfStrings = ['one', 'two', 'three'];
List<String>? aNullableListOfStrings;
List<String?> aListOfNullableStrings = ['one', null, 'three'];
print('aListOfStrings is $aListOfStrings.');
print('aNullableListOfStrings is $aNullableListOfStrings.');
print('aListOfNullableStrings is $aListOfNullableStrings.');
}
こちらの練習問題ですが、ポイントが2つあります。
-
List
そのものがnull
であることを許容させたい -
List
の要素にnull
があることを許容させたい
前者の対応するのが以下のコードです。
List<String>? aNullableListOfStrings;
後者はこちら。List
要素の型がString
で、?
がつくことによって要素がnull
の場合を許容させています。
List<String?> aListOfNullableStrings = ['one', null, 'three'];
The null assertion operator (!)
-
nullable typeがnullでないことを表現したいときは
!
を使う -
解答
int? couldReturnNullButDoesnt() => -3;
void main() {
int? couldBeNullButIsnt = 1;
List<int?> listThatCouldHoldNulls = [2, null, 4];
int a = couldBeNullButIsnt;
int b = listThatCouldHoldNulls.first!; // first item in the list
int c = couldReturnNullButDoesnt()!.abs(); // absolute value
print('a is $a.');
print('b is $b.');
print('c is $c.');
}
couldReturnNullButDoesnt()
関数とlistThatCouldHoldNulls
はint?
とあるので、nullableな型になっています。
以下でそれぞれから値を取得しようとしたときにint
型はnon-nullableな値のため、このままだとエラーになってしまいます。
なので、!
を使ってnullじゃないことをプログラムに教えてあげればOKです。
Type promotion
-
nullになる可能性がある変数でも、nullにならないのであれば、non-nullableな変数として扱う
-
Definite assignment : Dartでは変数がどこで割り当てられ読み込まれたかを追跡でき、non-nullableな変数に値が代入されているかを検証できる。
以下のif-else
文をコメントアウトすると、text
に値が代入されないままprint(text)
で使用されるためエラーになります。
void main() {
String text;
// 以下のif文をコメントアウトすると、エラーになる
if (DateTime.now().hour < 12) {
text = "It's morning! Let's make aloo paratha!";
} else {
text = "It's afternoon! Let's make biryani!";
}
print(text);
print(text.length);
}
late
キーワード
クラス変数やトップレベル変数はnon-nullableであるべきだが、値をすぐには代入できない場合がある。そういうときにはlate
を使います。
-
late
- あとで代入することを宣言できる。non-nullableな型で代入してなくてもエラーにならない
- 代入されずに値が使用されると
LateInitializationError
を投げる -
late
はfinal
と一緒に使うこともできる
-
解答
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);
}
-
final
と使う
class Team {
late final Coach coach;
}
class Coach {
late final Team team;
}
void main() {
final myTeam = Team();
final myCoach = Coach();
myTeam.coach = myCoach;
myCoach.team = myTeam;
print('All done!');
}
lazy initialization
late
をつけた変数は使用するときに初めて初期化されます
codelabのコードでみてみます。
_cache
にlate
をつけないで実行する。
int _computeValue() {
print('In _computeValue...');
return 3;
}
class CachedValueProvider {
final _cache = _computeValue();
int get value => _cache;
}
void main() {
print('Calling constructor...');
var provider = CachedValueProvider();
print('Getting value...');
print('The value is ${provider.value}!');
}
🔽
Calling constructor...
In _computeValue...
Getting value...
The value is 3!
_cache
にlate
をつけて実行する。
class CachedValueProvider {
late final _cache = _computeValue();
int get value => _cache;
int _computeValue() {
print('In _computeValue...');
return 3;
}
}
void main() {
print('Calling constructor...');
var provider = CachedValueProvider();
print('Getting value...');
print('The value is ${provider.value}!');
}
🔽
Calling constructor...
Getting value...
In _computeValue...
The value is 3!
前後の差分を取ってみます。
@@ -1,4 +1,4 @@
Calling constructor...
-In _computeValue...
Getting value...
+In _computeValue...
The value is 3!
late
をつける前はコンストラクタでインスタンスを生成したあとに_computeValue()
が呼び出されています。
late
をつけたあとは、getterで_cache
を取得するときに_computeValue()
が呼び出されて値が初期化されていることがわかります。
Discussion