[Kotlin] null安全とは? null許容型との付き合い方

今回はKotlinの特徴的な部分、null安全とnull許容型、そしてnull許容型からプロパティやメソッドを呼び出す方法についてお話ししたいと思います。

null安全 (Null Safety)とは?

JavaやKotlinはコンパイル時に型チェックが行われるため、「型安全」な言語ということができます。
要はコンパイルを通ったコードは「実行時に型のミスマッチが原因のエラーは起こらない」ということです。

同じように実行時にnullによるエラーが起こらないのがnull安全な言語となります。

Kotlinの型は基本的にnullを許さない

ではKotlinはどうやってnullによるエラーを回避しているのか?

例えばこのコードはKotlinコンパイラのnullチェックに引っかかり、エラーになります。

var check: String = "ハイボール1つ"
check = null
//エラー: Null can not be a value of a non-null type String
nullは(null非許容型である)String型の値になることはできない

Kotlinの型はStringであれIntであれ、全ての型は基本的にnullを代入できないnull非許容型です。

そもそもnullという値が代入されること自体を禁止することで、どこかで不意にnullが代入されてしまうようなコードをコンパイルの時点でチェックし、実行時のエラーを回避しています。

Javaではどうなる?

同じようなことをJavaでやってみると、少し違った結果が出てきます。

public class Main {
public static void main(String[] args) {
  String check = "ハイボール1つ";
  check = null;
  System.out.println(check)
    }
}
//null

この時点でエラーは起こりません。ハイボールの注文は後のnull代入によって取り消され、無かったことになりました。

ただnullが代入された後にString型のメソッドなどを使うとエラーになります。

String check = "ハイボール1つ";
check = null;
System.out.println(check.length());
//Exception in thread "main" java.lang.NullPointerException

「何も無い」「無」を示すnullはString型のメソッドなど持っていないので、その文字数を求めるメソッドがエラーになるのは当然。
これが「NullPointerException」、いわゆる「ぬるぽ」というやつです。

null許容型の変数を宣言する

とは言え、nullというもの自体は便利なものです。例えば変数を宣言して、とりあえず何かの値を入れなければならない場合などは、nullという特別な値が重宝されます。
そこで登場するのがnullを代入可能な型、null許容型の変数です。

var check: String? = "Kotlin"
check = null

型を指定するときに型パラメータの後ろに「?」を付けると、その型はnull許容型となり、nullを代入することが可能になります。

null許容型からメソッドを呼び出す方法

null許容型を使うことで、値がnullになり得る可能性については対応できます。ただこのnull許容型、そのままではほとんど何のメソッドやプロパティも利用できないことには注意が必要です。

var check: String? = "Kotlin"
println(check.length)

//エラー: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
null許容型のString?型からの呼び出しはセーフコール演算子(?.)や非null表明演算子(!!.)を利用する場合に限り認められる

null許容型からメソッドやプロパティを呼び出すには、ある特殊な手段を講じる必要があります。いくつかの方法があるので、早速見ていきましょう。

① セーフコール演算子

var check :String? = "Kotlin"
println(check?.length)
//6

?.lengthのようにメソッドやプロパティ呼び出しの.(ドット)の前に?を付ければ、呼び出し元がnullでない場合にのみメソッドやプロパティが呼び出されます。これを「セーフコール演算子」と呼びます。

セーフコール演算子を使用した場合、呼び出し元がnullであればその呼び出し自体が無視されます。エラーにはなりませんがcheck?.lengthの結果自体がnullになるので、その点には気をつけましょう。

② エルビス演算子(null合体演算子)

?:と表記するエルビス演算子(null合体演算子とも呼ばれます)を使う方法もあります。

(どうでもいいですが名前の由来はエルビス・プレスリー。「?」がリーゼント、「:」が目です。)


MEGAエルヴィス~エルヴィス・プレスリー・エッセンシャル・コレクション

var check: String? = null
println(check ?: "nullですやん")
//nullですやん

println()の中身にエルビス演算子を使い、変数の中身がnullでなければ演算子の左側、nullであれば右側が出力されます。

ここではnullが代入されているので、演算子の右側の文字列が出力されています。

セーフコール、let、エルビス演算子を組み合わせる

上記2つとスコープ関数letの組み合わせは、null許容型からメソッドを呼び出すのに使われるポピュラーな手段です。

val check: String? = "Kotlin"
val ans = check?.let{it + "かわいい"} ?: "Python"
println(ans)
//Kotlinかわいい

変数checkがnullでなければセーフコールによって何らかの処理を加え、もしnullであればエルビス演算子の右辺が出力されます。

③ スマートキャスト

ifwhenでnullチェックを行うスマートキャストも使用頻度が高い方法です。

val check: String? = null
if(check is String) println(check + "かわいい") else println("Python")
//Kotlinかわいい

ifの()内では変数がString型かどうかのチェックが行われていますが、その後に明示的なキャストは必要ありません。

ifがtrueのとき、その後の処理では「その値はString型である」として扱われるからです。これをスマートキャストといいます。

④ 二重感嘆符演算子(非null表明演算子)

こちらを使用するコードを実行時まで残すことは非推奨です

エクスクラメーション(ビックリマーク)2つをメソッドやプロパティ呼び出しの.(ドット)の前に付けることで、その値がnullであろうが何だろうが呼び出しが実行されます。

var check :String? = "Kotlin"
println(check!!.length)
//6

ただしこの演算子を使った場合、当然ながら値がnullであった場合はNullPointerExceptionになるので注意です。

おさらい

今回のポイントをおさらいしておきます。

  • Kotlinの型は基本的にnull非許容型である
  • nullになり得る型(null許容型)は型名の後ろに「?」を付ける
  • null許容型は、そのままではプロパティ参照やメソッド実行ができない
  • null許容型からプロパティやメソッドを使うためには、

    • セーフコール演算子
    • エルビス演算子
    • スマートキャスト

    などによって、ぬるぽを回避しつつ安全な呼び出しを行う。

タイトルとURLをコピーしました