2
0

More than 1 year has passed since last update.

kotlinのnullについて

Last updated at Posted at 2021-10-31

経緯

kotlinはjavaに比べて、随分とエンジニアに親切になったように思います。
個人的に嬉しいのは所謂null安全です。
これがあることで取り扱いがかなり楽になり、NullPointerExceptionなんて久しく見ておりません。
しかし最近になって知った 仕様 挙動(※) があったので、自戒を込めて基礎的な部分をまとめてみることにしました。
※2021/11/5 言語仕様ではなく、標準ライブラリでの定義による動作です。誤解を招く表現で申し訳ありませんでした。
参考にさせていただいた記事:【Kotlin】toString() 関数

なにがあったか

Sample.kt
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許容かどうかを明示的に記述する必要があります。

Sample.kt
// これはだめ
val hoge : String = null
// これはOK
val fuga : String? = null

「?」がついていない変数はnullでないと言えます。
ただし、プログラム上の不備でnullが入ってくると以下のようなエラーになります。

NullTestSampleKotlin.kt
class NullTestSampleKotlin {
    fun test(str: String) {
        Log.d("test", str)
    }
}
NullTestSample.java
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かもしれないですからね。

Sample.kt
val hoge : Int? = null

if (hoge != null) {
    // hogeをIntとして扱える
    val hogeLong = hoge.toLong()
}

// これはだめ。
val hogeLong2 = hoge.toLong()

セーフコール

以下のように「?」に続けて呼び出すことで、
nullでなかった場合のみ呼び出しが成功します。
失敗の場合はnullが返却されます。

Sample.kt
val hoge : Int? = null

// nullが返ってくる可能性があるので、hogeLongの型は「Long?」になります。
val hogeLong = hoge?.toLong()

めちゃくちゃ使います。

エルヴィス演算子

「?:」を用いることにより、左辺がnullだった場合に右辺を使用してくれます。
エルヴィス演算子が連続することもありますが、少しわかりづらくなりますね。

Sample.kt
// hogeがnullでない場合toLong()の結果、nullだった場合は-1LがhogeLongに代入されます。
// したがってhogeLongの型は「Long」に確定しています。
val hogeLong = hoge?.toLong() ?: -1L

あのロックな方に見えることからその名前がついているそうです。

終わりに

Javaよりもチェックが厳格になったことにより、記述するのは少しだけ面倒になりましたが
品質は良くなりやすいのかなあと思います。
たまにJavaのコードを書く際に混乱してしまうので、注意してコードを書いていきたいです。

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0