そもそもnull(ヌル)って?
null
というのは、『無(値が決まっていない・決められない状態)』であることを示す特別な値です。(細かい概念等は、プログラミング言語によって異なる場合があります)
『無』と言っても、数値の0(ゼロ)や、空の文字列(""
)とは異なる点に注意が必要です。
0(ゼロ)は、あくまで数値が0で表される状態であることを示すものであり、値が決まっていない(決められない)状態というわけではありません。
たとえば、ゲームアプリを作っていて、プレイヤーの所持金の初期値を0(ゼロ)に設定するとしましょう。
このとき、所持金の初期値が決められないから0にするわけではなく、明確に『所持金の初期値は0とする』ことを決めたからこそ、0を指定するわけですよね。
値が未決定の場合に、とりあえず0を指定しておくようなケースもありますが、『0=値が決められない場合に限定して使われる』というものでは決してありません。
空の文字列(""
)に関しても、これは『テキストを代入できるところを確保している』というような状態であり、言い換えれば、何らかのテキストが代入されることが分かりきっていることになります。
こちらもやはり、nullが意味するような、値が全く決められない『無の状態』とは異なります。
繰り返しになりますが、 null = 無(値が決められない)状態を示す特殊な値 ということをまずしっかりイメージとして定着させておくことが大切です。
そんな null は様々な場面で使用されますが、主に次のような場面で使われることが多いです。
- (1)外部APIから返されるデータのうち、該当データがなかった場合に null が返される
- (2)変数の初期値を決めておくことができない場合に、初期値を null にしておく
この記事では、(2)のケースを想定して解説を進めていきます。
たとえば、毎日その日に使ったお金を記録するお小遣い帳アプリをイメージしてみてください。
このとき、初期値が0(ゼロ)では少しマズイですよね。
というのも、お金を使わない(入力値が0である)日も当然あり得るので、初期値が0では未入力状態なのか、入力した上で0なのかが判別できないためです。
基本的に、null を使わずに済むならその方が良いのですが、このような場合は null をうまく活用した方が状態が分かりやすくなります。
次のステップでは、Kotlinでnullを扱うシンプルなコードを紹介しながら、null への理解を深めていきたいと思います。
POINT!
- ・nullは、値が決まっていない(決められない)”無”の状態を表す特別な値!
- ・nullは、0(ゼロ)でも、空の文字列でもない!
- ・API連携で返されるデータにnullが含まれているケースの他、どうしても初期値が決められない場合にnullを利用するケースがある!
Kotlinはnull安全を備えている
Kotlinの特徴の一つに、『null安全(Null Safety)』と呼ばれるものがあります。
これは簡単に言ってしまえば、『nullは特別な値で様々なエラーの原因になっちゃうので、nullの扱いには制限をかけますよ & 値がnullかどうか厳密なチェックを行いますよ』…といった仕組みのことです。
Kotlinにおけるnull安全に関しては、次のような機能(制限)があります。
- ・変数の型が指定されている場合、nullは通常の手段ではその変数に代入できない
- ・基本的に、nullの可能性がある変数に対しては、その可能性が残されたままメソッドやプロパティを利用したりすることができない
このあたりは、実際にコードを書いて実行してみた方がわかりやすいと思うので、さっそく例を見ていきましょう。
下のコードは、Int型の変数に null
を代入しようとしているので、Kotlinのnull安全機能に引っかかり、コンパイルエラーになります。
var expenses:Int = null // error
では、絶対にこの変数 expenses
には null
を代入できないのかというと、そういうわけではありません。
データの型に ?
マークを付け加えることで、null許容にする(= null
を代入可能にする)ことができます。
var expenses:Int? = null // OK
このように、開発者が意図的にnull許容に設定しない限り、不意に null
が代入されてしまうことがないことが、プログラムの安全性の向上に繋がっています。
また、Int型には様々なメソッドが用意されていますが、値が null
の可能性が残されているままで、そういったメソッドを実行することはできません。
たとえば、Int.equals()
という、数値が他のある数値と等しいかどうかを調べるためのメソッドをそのまま使おうと思っても、エラーになってしまいます。
var expenses:Int? = null // OK
val isEqual = expenses.equals(100) // error
println(isEqual)
この問題に対する解決策は、次のステップでご紹介していきます。
POINT!
- ・Kotlinは、null安全(Null Safety)を備えている!
- ・?マークを使って意図的に指定しない限り、基本的にnullを代入することはできない!
- ・nullの可能性がある値に対し、そのメソッドやプロパティにそのままアクセスすることは基本的にできない!
nullの可能性がある変数のメソッドを利用する
nullの可能性がある変数のメソッドやプロパティを利用する方法でオーソドックスなのが、『条件分岐でnull の可能性を否定する』という方法です。
次のように、if文の条件として、変数が null
ではないことを指定すれば、そのブロック内では変数が null
ではないことが決定されるので、エラーにはなりません。
var expenses:Int? = null // OK
if(expenses != null) {
val isEqual = expenses.equals(100)
println(isEqual)
} else {
println("value is null")
}
// Output: value is null
上のサンプルの場合、if文の条件を満たさないためifブロックはそもそも実行されず、elseブロックの処理のみが実行されることで、エラーが回避されています。
また、セーフコール(Safe calls)と呼ばれる方法を用いることでも、null
の可能性がある変数のメソッド等を利用することができます。
セーフコールを利用するには、.?
演算子を使います。
var expenses:Int? = null // OK
val isEqual = expenses?.equals(100)
println(isEqual) // null
上の結果から分かるように、セーフコールを利用してメソッド等にアクセスした結果、前提となる変数が null
であった場合は null
が返されるようになっています。
null の可能性がある変数のメソッドを利用 → その結果も null かもしれないので、再びセーフコール演算子で次のメソッドを利用 …というふうに、下のようにチェーンのような形になることもよくあります。
propA?.methodA()?.methodB()?.propB
やや強引ですが、今回の例を使ってセーフコールのチェーンを表現した例が下です。
var expenses:Int? = null // OK
val isEqual = expenses?.equals(100)?.compareTo(true)?.toString()?.length
println(isEqual) // null
変数 expenses
が null
なので、結局、null
が返されていることが確認できますね。
また、プログラムコード上では null
の可能性があるとされているが、開発者が絶対に null
の可能性はないと断定できる場合、!!
演算子を使って、強制アンラップすることも可能です。
var expenses:Int? = 100
val isEqual = expenses!!.equals(100)
println(isEqual) // true
上のサンプルの場合、null許容にしているものの、実際に代入されている値は 100
であり、それ以降、他の値は変数に代入されていません。
よって、変数 expenses
はnull許容値ではあるものの、実質的に null
の可能性はありません。
このような場合であれば、!!
演算子で強制アンラップしてしまっても大丈夫です。
ですが、null
の可能性が残されている場合、null参照によるエラーの原因となってしまう恐れがあるので、できる限り強制アンラップは避けた方が無難だと言えます。
POINT!
- ・nullの可能性が残される場合、条件分岐でnullの可能性を除去すると処理できる!
- ・セーフコール(.?)を利用することで、安全にnullの可能性がある値のメソッドやプロパティにアクセスできる!
- ・100%nullではないと断言できる場合、!! 演算子で強制アンラップすることも可能!
エルビス演算子
これまでKotlinにおける null
の扱いの基本について解説してきましたが、最後に『エルビス演算子』と呼ばれる、便利な記法をご紹介します。
null
の可能性がある場合、基本的にはif文で条件判定を行い、null
の可能性を消去することが求められますが、if文だとどうしてもコードの行数が増えて記述量が多くなってしまいます。
セーフコール演算子(?.
)を使えばif文を書かずに済みますが、セーフコールのみでは値が null
だった場合の処理を追加できません。
この問題を解決してくれるのが、エルビス演算子( ?:
)です。
エルビス演算子を使った構文は下のようになります。
null かもしれない値 ?:
値がnullだった場合の処理
expenses
が null
ではなかったらその値を、null
ならメッセージテキストを出力する関数を、エルビス演算子を使って定義した例が下です。
var expenses:Int? = null
fun printExpenses() {
println(expenses ?: "value is null")
}
printExpenses()
// Output: value is null
当然、null
でない値を指定すれば、その値が出力されます。
var expenses:Int? = 100
fun printExpenses() {
println(expenses ?: "value is null")
}
printExpenses()
// Output: 100
ちなみに、エルビス演算子の名前の由来は、[ ?:
] このマークがエルビス・プレスリーの髪型に似ているかららしいのですが…筆者としては、よくわかりませんでした。笑
おそらく、?マークの膨らんだ部分がリーゼントヘアーの膨らみの部分を表しているのではないかと思いますが、違ったらすいません💦
ともかく、以上がKotlinにおける null
の基本です。
Kotlin + Jetpack Compose でシンプルなAndroidアプリを開発するぐらいであれば、これぐらいの基礎知識でも十分です。
Kotlinはnull安全を備えているので、特別にnullを意識しなくても、安全にアプリ開発を行えるのはありがたいポイントですね!
POINT!
- ・エルビス演算子を利用することで、値がnullだった場合の処理を柔軟に設定できる!
- ・エルビス演算子は、[ ?: ] で表す!
- ・シンプルなAndroidをアプリ開発を行う上では、nullに関してはこれぐらいの基礎知識でもOK!