経緯
kotlinはjavaに比べて、随分とエンジニアに親切になったように思います。
個人的に嬉しいのは所謂null安全です。
これがあることで取り扱いがかなり楽になり、NullPointerExceptionなんて久しく見ておりません。
しかし最近になって知った 仕様 挙動(※) があったので、自戒を込めて基礎的な部分をまとめてみることにしました。
※2021/11/5 言語仕様ではなく、標準ライブラリでの定義による動作です。誤解を招く表現で申し訳ありませんでした。
参考にさせていただいた記事:【Kotlin】toString() 関数
なにがあったか
val hoge: Int? = null
if (hoge != null) {
Log.d("test", "hoge is not null.")
} else {
Log.d("test", "hoge is null.")
}
val fuga = hoge.toString()
if (fuga != null) {
Log.d("test", "fuga is not null.")
if (fuga == "null") {
Log.d("test", "fugaValue is \"null\".")
} else {
Log.d("test", "fugaValue is $fuga.")
}
} else {
Log.d("test", "fuga is null.")
}
// 以下は確認のため
val piyo: String? = null
if (piyo != null) {
Log.d("test", "piyo is not null.")
if (piyo == "null") {
Log.d("test", "piyoValue is \"null\".")
} else {
Log.d("test", "piyoValue is $piyo.")
}
} else {
Log.d("test", "piyo is null.")
}
上記のようなものがあったとして、
nullの可能性があるものに対してtoString()の時点でそもそもエラーになると思ったのですが
そんなことはなく・・・。実行すると以下が出力されます。
2021-11-05 09:38:23.816 3289-3289/com.example.testapplication D/test: hoge is null.
2021-11-05 09:38:23.816 3289-3289/com.example.testapplication D/test: fuga is not null.
2021-11-05 09:38:23.816 3289-3289/com.example.testapplication D/test: fugaValue is "null".
2021-11-05 09:38:23.816 3289-3289/com.example.testapplication D/test: piyo is null.
hogeはnull、fuga(hoge.toString()したもの)はnullではない、fugaの中身(fugaValue)は文字列"null"という結果になりました。piyoはちゃんとnullです。
したがって、kotlinのtoString()は安全、ということです。
(文字列の"null"が返ってくるのは少し嫌な気持ちになりますね・・・。チェックする際は注意しなくてはいけません。)
ただし、toInt()などはだめです。nullは数値にできませんしね。
サンプル
kotlinのnull周りの仕様について少しだけまとめます。
型宣言
kotlinはnull許容かどうかを明示的に記述する必要があります。
// これはだめ
val hoge : String = null
// これはOK
val fuga : String? = null
「?」がついていない変数はnullでないと言えます。
ただし、プログラム上の不備でnullが入ってくると以下のようなエラーになります。
class NullTestSampleKotlin {
fun test(str: String) {
Log.d("test", str)
}
}
public class NullTestSample {
public void test() {
NullTestSampleKotlin nullTestSampleKotlin = new NullTestSampleKotlin();
nullTestSampleKotlin.test(null);
}
}
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null
kotlinでメソッドを定義、引数のStringなどに「?」をつけなかった場合、
kotlinからは引数がnon-nullなことはわかりますが、Javaからはわからずにnullを渡せてしまいます。
Javaからkotlinに移行する際などは注意しましょう。
スマートキャスト
nullでないことが確認できていれば、安全に使うことができます。
そこをよしなにやってくれるのがスマートキャストです。
以下の例で、nullチェックをしたブロックの中であればhogeがIntであることは確定しているので、toLong()などを呼び出せます。
ブロック外の呼び出しはコンパイルエラーになります。nullかもしれないですからね。
val hoge : Int? = null
if (hoge != null) {
// hogeをIntとして扱える
val hogeLong = hoge.toLong()
}
// これはだめ。
val hogeLong2 = hoge.toLong()
セーフコール
以下のように「?」に続けて呼び出すことで、
nullでなかった場合のみ呼び出しが成功します。
失敗の場合はnullが返却されます。
val hoge : Int? = null
// nullが返ってくる可能性があるので、hogeLongの型は「Long?」になります。
val hogeLong = hoge?.toLong()
めちゃくちゃ使います。
エルヴィス演算子
「?:」を用いることにより、左辺がnullだった場合に右辺を使用してくれます。
エルヴィス演算子が連続することもありますが、少しわかりづらくなりますね。
// hogeがnullでない場合toLong()の結果、nullだった場合は-1LがhogeLongに代入されます。
// したがってhogeLongの型は「Long」に確定しています。
val hogeLong = hoge?.toLong() ?: -1L
あのロックな方に見えることからその名前がついているそうです。
終わりに
Javaよりもチェックが厳格になったことにより、記述するのは少しだけ面倒になりましたが
品質は良くなりやすいのかなあと思います。
たまにJavaのコードを書く際に混乱してしまうので、注意してコードを書いていきたいです。