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は生成にコストがかかる
}
}