Kotlin の null 許容型 (nullable type) の変数は、if 等の制御文を挟むことで、null 非許容型 (non-null type) に自動でキャストされる。
null 許容型はコンパイル時にしか存在しないので、バイトコードにキャスト処理が入るわけではない。
IntelliJ IDEA だと、自動キャストされた変数はハイライトされて、マウスオーバーすると「Smart cast to 型名」がポップアップされるので分り易い。
ここでは、どんな制御文で null 許容型からのスマートキャストが可能か(コンパイラがどこまでやれるのか)、色々試してみる。
Kotlin 1.0.1 なので、バージョンが上がると挙動が変わるかも。
null 許容型はコンパイル時にしか存在しないので、バイトコードにキャスト処理が入るわけではない。
IntelliJ IDEA だと、自動キャストされた変数はハイライトされて、マウスオーバーすると「Smart cast to 型名」がポップアップされるので分り易い。
ここでは、どんな制御文で null 許容型からのスマートキャストが可能か(コンパイラがどこまでやれるのか)、色々試してみる。
Kotlin 1.0.1 なので、バージョンが上がると挙動が変わるかも。
1 変数の場合
- if で null 除外
基本。fun ifNotNull(x: Any?) { if( x != null ) println(x.javaClass) }
- if で null の場合、return
(1) の逆バージョン。fun ifNullReturn(x: Any?) { if( x == null ) return println(x.javaClass) }
- when で null 除外
(1) の when バージョン。if 使った方がいい。fun whenNotNull(x: Any?) { when( x ){ null -> {} else -> println(x.javaClass) } }
- when で null の場合、return
(2) の when バージョン。これも if 使った方がいい。fun whenNullReturn(x: Any?) { when( x ){ null -> return else -> {} } println(x.javaClass) }
また、else を省略するとコンパイルエラー( .javaClass で)。 - エルビス演算子 ?: で null の場合、return
(2) のエルビス演算子バージョン。fun elvisNullReturn(x: Any?) { x ?: return println(x.javaClass) }
少し魔術っぽいけど、慣れたら問題ない・・・? タイプ量が少なくなるから使いそう。
return の代わりに例外 throw も可。 - ぬるぽ演算子 !! で null の場合、例外
ほとんどの場合、バッドプラクティス。(ぬるぽ演算子の使い所は難しい・・・)fun nullpo(x: Any?) { x!! println(x.javaClass) }
※ !! 演算子は特に名前が無いようなので、"ぬるぽ演算子"の呼び方は非公式 - while で null 除外
(1) の while バージョン。使い所はあまり無い気がするけど、一応可能。fun whileNotNull(x: Any?) { while( x != null ){ println(x.javaClass) break } }
- while で null の場合、return
(2) の while バージョン。使い所なんて無い気がするけど、一応可能。fun whileNullReturn(x: Any?) { while( x == null ) return println(x.javaClass) }
- [NG] if-true ネストで if-return
ありえないコードは、ネストの中まで見てくれない模様。fun ifNullReturnIfNest(x: Any?) { if( true ){ if( x == null ) return } println(x.javaClass) // コンパイルエラー }
- while-true ネストで if-return
if と異なり while ならネストの中まで見てくれる・・・。fun ifNullReturnWhileNest(x: Any?) { while( true ){ if( x == null ) return break } println(x.javaClass) }
while の条件を変数にすると、コンパイルエラー。
たぶん、while-true がありえるかもしれないコードだからだと推測。 - [NG] when-true ネストで if-return
これもありえないコードなので不可。when に else があっても不可。fun ifNullReturnWhenNest(x: Any?) { when { true -> if( x == null ) return } println(x.javaClass) // コンパイルエラー }
- [NG] with 関数のラムダ式で if-return
VB でお馴染みの With だが、Kotlin では関数 + ラムダ式になっている。(組み込みの構文ではない。)fun ifNullReturnWithScope(x: Any?) { with( x ){ if( x == null ) return // この return は ifNullReturnWithScope を抜ける // 本来、with 内で x にアクセスする際は this を使う } println(x.javaClass) // コンパイルエラー }
with 関数はインライン展開されるけど、さすがに関数の中まで見てくれない。
2 変数の場合
- if で null 除外
単純な複合条件は可能。fun ifNotNull(x: Any?, y: Any?) { if( x != null && y != null ){ println(x.javaClass) println(y.javaClass) } }
引数無し when でも同様に可能。 - if で null の場合、return
(1) の逆バージョン。fun ifNullReturn(x: Any?, y: Any?) { if( x == null || y == null ) return println(x.javaClass) println(y.javaClass) }
引数無し when (else 有り) でも同様に可能。 - if-else-if で順番に null 除外
一度 null を除外すれば、次のブロックでも有効。fun ifNotNullEach(x: Any?, y: Any?) { if( x == null ){ // x は null、y は null かも } else if( y == null ){ // x は 非 null、y は null println(x.javaClass) } else { // x、y ともに非 null println(x.javaClass) println(y.javaClass) } }
引数無し when でも同様に可能。 - [NG] if-else-if で順番に複合条件で null 除外
コンパイラが複雑になるから見てないのだと推測。特に変数が増えた場合やばそう・・・fun ifNotNullComplex(x: Any?, y: Any?) { if( x == null && y == null ){ // x、y ともに null } else if( x != null && y == null ){ // x は 非 null、y は null println(x.javaClass) } else if( x == null && y != null ){ // x は null、y は 非 null println(y.javaClass) } else { // x、y ともに非 null のはず println(x.javaClass) // コンパイルエラー } }
人間が見ても else ブロックで null 除外されていることは、分かりづらいと思う。