Dartはバージョン2.12より 健全なNull Safety(Sound Null Safety) を言語レベルで導入しました。
現代のプログラミングにおいて、NullPointerException(Null参照エラー)は最も頻発するバグの1つです。
Dartの健全なNull Safetyは、コードを書いている最中やコンパイル時にNullになり得る箇所を厳密に区別し、実行時クラッシュを未然に防ぎます。
この章では、DartのNull Safetyの仕組み、各種Null認識演算子、そして late 修飾子の正しい使い方をマスターします。
?)とNon-nullable型Dartの型システムでは、すべての型がデフォルトで Non-nullable(Null非許容) です。
値として null を許可したい場合のみ、型の後ろに ? を付けて Nullable(Null許容) として明示的に宣言します。
void main() {
// 1. Non-nullable型 (デフォルト): null を代入できない
String nonNullableText = 'Hello';
// nonNullableText = null; // コンパイルエラー!
// 2. Nullable型 (末尾に ? を付与): null を代入できる
String? nullableText = 'Hello';
nullableText = null; // OK
print('nonNullableText: $nonNullableText');
print('nullableText: $nullableText');
}dart run nullable_basics.dartnonNullableText: Hello nullableText: null
Nullableな変数であっても、if 文などで null でないことをチェック(フロー解析)すると、そのスコープ内では自動的にNon-nullable型へと昇格(プロモート)します。
void printLength(String? text) {
if (text != null) {
// このブロック内では text は String? ではなく String として扱われる
print('文字数: ${text.length}');
} else {
print('テキストは null です');
}
}
void main() {
printLength('Dart Flutter');
printLength(null);
}dart run flow_analysis.dart文字数: 12 テキストは null です
!)とその危険性Nullアサーション演算子(!) は、コンパイラに対して「この値はNullable型だが、実行時には絶対に null ではないとプログラマが保証する」と伝える演算子です。
void main() {
String? maybeName = 'Alice';
// ! を付けることで String 型として強制的に扱う
String definiteName = maybeName!;
print('名前: $definiteName');
}dart run null_assertion.dart名前: Alice
もし値が null であるにもかかわらず ! を適用した場合、実行時に TypeError 例外が発生してプログラムがクラッシュします。
?.、??、??=)Dartには、Nullableな値を安全かつ簡潔に処理するための Null認識演算子(Null-aware operators) が用意されています。
これらを活用することで、冗長な if (x != null) チェックを大幅に減らすことができます。
?.)対象が null でなければプロパティやメソッドにアクセスし、null であれば null を返します。
void main() {
String? text;
// text が null なので length にアクセスせず null を返す
int? length = text?.length;
print('length (null時): $length');
text = 'Hello';
length = text?.length;
print('length (値あり時): $length');
}dart run null_aware_access.dartlength (null時): null length (値あり時): 5
??)左辺が null でない場合は左辺の値を、null の場合は右辺のデフォルト値を返します。
void main() {
String? userName;
String displayName = userName ?? '名無しのユーザー';
print('表示名: $displayName');
userName = 'Alice';
displayName = userName ?? '名無しのユーザー';
print('表示名: $displayName');
}dart run null_coalescing.dart表示名: 名無しのユーザー 表示名: Alice
??=)変数が null の場合のみ、右辺の値を代入します。
void main() {
int? count;
count ??= 10; // null なので 10 を代入
print('count: $count');
count ??= 20; // 既に 10 なので 20 は代入されない
print('count: $count');
}dart run null_aware_assignment.dartcount: 10 count: 10
late 修飾子の仕組みと使いどころlate 修飾子 は、Non-nullableな変数の初期化を「宣言時ではなく、後から(あるいは必要になった時に)行う」ことをコンパイラに宣言するキーワードです。
宣言時点では値が決まらないNon-nullable変数の保持や、重い計算の遅延評価に利用されます。
late による初期化の遅延とLazy評価late 変数に初期化式を記述すると、その変数に初めてアクセスされた瞬間にのみ計算が行われます(Lazy評価)。
String heavyComputation() {
print('-> 重い計算を実行中...');
return '計算結果: 42';
}
void main() {
print('プログラム開始');
late String lazyData = heavyComputation(); // この時点ではまだ実行されない
print('データが必要になりました:');
print(lazyData); // ここで初めて heavyComputation が呼ばれる
print('二度目の参照: $lazyData'); // 既に計算済みなので再実行はされない
}dart run late_lazy.dartプログラム開始 データが必要になりました: -> 重い計算を実行中... 計算結果: 42 二度目の参照: 計算結果: 42
この章では、Dartの堅牢性を支える Null Safety について学びました。
null を代入できない。null を許容したい場合は String? のように ? を付ける。if (x != null) などのチェックを行うと、自動的にNon-nullable型にプロモートされる。!): 実行時エラーのリスクがあるため、どうしても必要な場合を除き使用を避ける。?.: null でない時だけプロパティ/メソッドを呼び出す。??: null の場合のデフォルト値を指定する。??=: 変数が null の時だけ代入する。late 修飾子: Non-nullable変数の初期化を遅延させたり、初回アクセス時の遅延評価(Lazy load)を行う。次の 第3章 では、Dartにおける関数、名前付き引数、およびクロージャについて学びます。
ユーザープロフィールからメールアドレスのドメイン部分を取得し、取得できない場合はデフォルト値を返すプログラムを作成してください。
String? email を定義し、最初は 'user@example.com' を代入する。email が null の場合は 'ドメイン不明' を返す安全な処理を書く。
email?.split('@').last ?? 'ドメイン不明'email = null を代入し、再度同じ処理を実行して 'ドメイン不明' が出力されることを確認する。void main() {
// ここにコードを書いてください
}dart run practice2_1.dart設定マップの初期化と、値の取得・更新を行うプログラムを作成してください。
late String appTitle を定義し、初回アクセス時に 'My Dart App' を生成する初期化式を記述する。Map<String, String>? config 変数を null で定義する。??= 演算子を使って、config が null の場合に {'theme': 'dark'} を代入する。appTitle と config の中身を出力する。void main() {
// ここにコードを書いてください
}dart run practice2_2.dart