Edited at

Java開発者に送るKotlinのNull安全


null許容型とnull非許容型

Kotlinの型システムはnullになり得る型と、

なり得ない型を区別します。

nullになり得る型は型名の後に?をつけます。

val num: Int = null // コンパイルエラー nullになり得ない

val num: Int? = null // null許容型であればOK

null許容型の関数やプロパティに直接アクセスすることは出来ません。

コンパイルエラーとなります。

val num: Int? = null // null許容型として宣言

val floatNum = num.toFloat() // コンパイルエラー

nullかも知れない場合はアクセスできない ということは、

NullPointerExceptionのリスクが大きく軽減されます。


安全呼び出し

Javaでこのようなnullチェックは良く見かけるかと思います。

// Java

Dog dog = //nullかも知れない
dog.run(); // NullPointerExceptionの可能性アリ
if (dog != null) {
dog.run(); // Nullチェック済み
}

同じことをKotlinでも実装してみます。

val dog: Dog? = //nullかも知れない

dog.run() // コンパイルエラー
if (dog != null) {
dog.run() // nullチェック済みのブロックではOK
}

null許容型オブジェクトの関数は直接呼び出すことが出来ません。

nullチェック済みのifブロックの中ではnull非許容型に自動的にキャストされ、

関数を呼び出すことが出来ます。

これをスマートキャストと言います。

また、Kotlinにはもっと便利な糖衣構文があります。

val dog: Dog? = // nullかも知れない

dog?.run()

?.に続けて関数の呼び出しやプロパティにアクセスできます。

上の例だとdogがnullだった場合は、ただnullが返されるだけです。

これを安全呼び出しと言います。


安全キャスト

Javaでこのようなダウンキャストのコードを見かけるかと思います。

/*

* DogクラスはAnimalクラスのサブクラスです
*/

Animal animal = // ...

((Dog)animal).run(); // ClassCastExceptionの可能性アリ

if (animal instanceof Dog) {
Dog dog = (Dog)animal;
dog.run();
}

同じことをKotlinで実装してみます。

/*

* DogクラスはAnimalクラスのサブクラスです
*/
val animal: Animal = // ...

(animal as Dog).run() // // ClassCastExceptionの可能性アリ

if (animal is Dog) {
animal.run()
}

Kotlinのキャストはas演算子を使います。

キャストに失敗るすとClassCastExceptionがスローされます。

Javaのinstanceof相当するのはis演算子です。

こちらもスマートキャストによって、

ifブロックの中で変数animalはDog型に自動的にキャストされ、

Dogクラスのメソッドがそのまま呼び出せます。

また、Kotlinにはもっと便利な糖衣構文があります。

val animal: Animal = // ...

(animal as? Dog)?.run()

as?演算子によって、上の例ですとDog?型にキャストされます。

キャストできない場合はnullが返るため安全です。


非null表明

安全呼び出し(.?をつける) 以外にnull許容型を扱う方法があります。

val num: Int? = // nullかも知れない

val floatNum = num.toFloat() // コンパイルエラー
val floatNum = num!!.toFloat() // コンパイルOK しかしNullPointerExceptionの可能性アリ

.!! を付けます。

必要な場面もありますが、極力使うべきではないでしょう。


エルビス演算子

Javaでこのような分岐処理をよく見かけるかと思います。

// Java

Dog dog = getDog(); // getDogの戻り値はnullかも知れない
if (dog == null) {
dog = new Dog();
}
dog.run();

kotlinには「もしnullだったら」の時に便利な演算子があります。

    val dog = getDog() ?: Dog()

dog.run()

?:エルビス演算子の

左側にはnull許容型の変数

右側にはnullだった場合に実行される処理を記述します。

代替オブジェクトの生成やExceptionをthrowするのが一般的でしょう。


非null許容型のクラスプロパティ

クラスのプロパティを宣言する際に、null許容型にせざるを得ない場合があるかと思います。

コンストラクタをオーバーライド出来ないフレームワークのクラス、

あるコールバックの後でなければ初期化出来ないプロパティなど...

しかしそれらは通常、null許容型で扱うのは面倒で、実質的に非null許容型で扱いたい場合があります。 (実際にKotlinで開発しているとよくあります)

そんな時はlateinit修飾子が便利です。

// ログイン用のViewクラス

class LoginView {
// TextViewやButtonなどの子要素を持つが、
// これらはリソースから生成される...という仮想UIシステム
lateinit var emailTextView: TextView
lateinit var passwordTextView: TextView
lateinit var loginButton: Button

// Viewがリソースからロードされると子要素が取得可能となる
fun onLoadViewFromResource(viewResource: View) {
this.emailTextView = viewResource.children[0]
this.passwordTextView = viewResource.children[1]
this.loginButton = viewResource.children[2]
}
}

lateinit修飾子は、初期化を遅らせることが出来ますが、

初期化前にプロパティにアクセスするとExceptionとなるので注意が必要です。

この他に、lazyというプロパティデリゲートがあります。

初期化にコストのかかるプロパティなどを遅延初期化させる時に便利です。

class LoginView {

val loginConnection: LoginConnection by lazy {
LoginConnection() // LoginConnectionは生成にコストがかかる
}

}