はじめに
本記事ではNullableとNon-nullableについてポイントをまとめていきます。
Null(ヌル)は何のオブジェクトも参照していないということを意味する値で、ときに便利な概念であります。値が存在していないときを示す場合などですね。
しかしNullの値をオブジェクトとして使おうとするとエラーが発生してアプリがクラッシュするため、オブジェクトを使用するときに、それがNullではないということをいちいち確かめる必要が出てきます。これがコードを必要以上に複雑にしてしまうというのが大きな問題です。
そこでDart2.10以降、何も言わないで変数を宣言すればNon-nullable(=nullではない)になるという規則が導入されました。Nullかも知れない変数の型には「?」をつける必要があります。「?」という不安定な表記がなかなか意地悪でいいですよね。
こうすることで、「?」がついていない型については、Nullかどうかのチェックを行わずにスッキリとしたコードを書くことができます。
nullでないことを保証する型
Dart2.10以降の、つまり現在のDartはデフォルトではnullを許容していない型になっています。
なので単にintやString型の変数を宣言した場合、必ず初期化をしてnullでない値にする必要があります:
int a; //初期化の値を入れていない=null print(a); //そのままaを参照→コンパイルできず
ここでは2行目のprintがaの値を参照したとき初期化されていない、ということでコンパイル時にエラーとして弾かれます。1行目の宣言時に必ずしも値を入れなくてはいけないということではありませんが、参照される(使われる)前には必ず初期化する必要があります。
int a = 1; void main(){ if(a != null){ //このnullチェックは必要ない print(a); } }
int型はnon-nullableので、aはnullでないことはコンパイラが保証しています。なので上記のようにaに対してnullチェックを行う必要はありません。
nullを許容するには、型宣言のところに「?」を付けます。ただしこれはどうしても初期化できない、あるいは「値がない」ことをnullによって示す必要があるという場合にとどめ、なるべくnon-nullable型を用いたほうがコードの可読性が上がります。
int? a; //nullかもしれない型として宣言 print(a); //nullが出力される
nullableからnon-nullableな型への変換
nullableな変数とnon-nullableな変数の橋渡しとして「!」演算子があります。これはnullableな型宣言の変数ではあるもののnullが入っていないと確信できる状況で用い、non-nullableな型に変換するものです。
int? a; //aはnullableなint a = 0; //aに値が必ず入る int b = a!; //bはnon-nullable。これに!演算子を付けてnullableの値を入れる print(b);
しっかりnullチェックを行って絶対にnullではないと分かっている状況でも、コンパイラがnullかもしれないと判断している場合があります。このときは「!」を付けて強くnullではないと宣言していきます。
またもしa!としたときにaにnullの値が入っていると、実行時の型チェックでTypeErrorがスローされます。
もう一つの変換方法として「??」演算子を使うものがあります。「x ?? y」はxがnullでなければx、xがnullであればyとなる式です。ですので、下記のような変換が成立します。
int? a; ... int b = a ?? 0;
aがnullの場合は0を代わりの値として代入することでbがnullではないことを保証しています。
メソッド呼び出しとnullable
インスタンスのメソッドを呼び出す「.」演算子はnullの可能性がある変数には使用できません。(事前にnullチェックをしてnullの可能性を排除している場合はnullableな変数にも「.」演算子は使えます。)
nullの可能性がある変数に「.」演算子を適用したい場合は「?.」を用います。これによりもしnullに対してメソッド呼び出しを行った場合、結果がnullになるだけでアプリ全体をクラッシュさせるエラーは回避できます:
double? a; int? b = a?.ceil(); //aがnullableなので「?.」でメソッド呼び出し print(b); //ここではnull
aがnullかも知れないので?.演算子でメソッドを読んでいます。aがnullの場合、bはnullになります。
これを用いるとnullableな変数でもnullチェックが頻発せずにすっきりしたコードを書くことが可能になります。
ただあまりnullが伝搬していくことを放置することはあまりなく、どこかで??などを用いてnon-nullableにしていくのが普通です。
おわりに
C言語の出てきた初期の頃はポインタのアドレスの値が0であるものをヌルポインタといいました。アドレスとは計算機上のメモリの番地で0は先頭中の先頭で、通常のプログラムでここをポインタとして指すことはありえず、存在しない値として使われたわけです。
その後、Javaで「null」というリテラルが登場、「ぬるぽ」の愛称で親しまれたNull Pointer Exceptionに悩まされるのはJavaの学習者が必ず通る道でした。
その後、プログラミング技法の発達で「Nullオブジェクトパターン」というデザインパターンが見いだされnullという値ではなく通常のオブジェクトでNullを表していくプログラミングスタイルが推奨されました。このNullオブジェクトパターンの効能を言語仕様として取り込んだのが、NullableとNon-nullableの仕組みというわけですね。
これによって余計な分岐と余計なクラッシュを削減することができます。
