LoginSignup
48
12

存在しない彼女へのアクセスによって起きるぬるぽをKotlinのNull Safetyの力で防ぐ

Last updated at Posted at 2023-02-13

今日はなんの日?

今日はバレンタインらしいですが、特に変わったことがないのでいつも通りの記事を書きたいと思います。

とあるコード

import java.util.Calendar;

class Chocolate {}

class Kanojo {
    private Chocolate choco = new Chocolate();

    Chocolate getChoco() {
        return choco;
    }
}

class Naotiki {
    private Kanojo kanojo;
    Kanojo getKanojo() {
        return kanojo;
    }

    void eat(Chocolate choco){
        System.out.println("おいし~");
    }

    public static void main(String[] args) {
        //私のインスタンスを生成
        Naotiki naotiki= new Naotiki();
        Calendar calendar = Calendar.getInstance();
        //バレンタインなら?
        if (calendar.get(Calendar.MONTH) == Calendar.FEBRUARY && calendar.get(Calendar.DAY_OF_MONTH) == 14) {
            //チョコがもらえる・・・?
            Chocolate choco = naotiki.getKanojo().getChoco();
            naotiki.eat(choco);
        }
    }
}

ふむふむ・・・
main()が実行されると私のインスタンスが作られ、もし2/14なら私のKanojoを取得しチョコをもらうんだな!
そして私がありがたく美味しく頂くということか・・・
完璧なコードだな。間違いない。

さて、実行してみるか。

例外
Exception in thread "main" java.lang.NullPointerException: 
Cannot invoke "Kanojo.getChoco()" because the return value of "Naotiki.getKanojo()" is null
	at Naotiki.main(Valentine.java:32)

あれ・・・?

意訳

ぬるぽ
Naotiki.getKanojo()の返り値がNullなのでKanojo.getChoco()が実行できませんでした

あ・・・・(察し)
うわぁぁぁぁぁぁぁぁ!!!!!!(ぬるぽでクラッシュする音)

なんということでしょう。
彼女がNullの可能性を考慮しなかったが為に、2/14にぬるぽでクラッシュしてしまう悲しい私が生まれてしまったのです。
これでは一生2/14に怯えて暮らさなければなりません。

これでは良くないですね。
Kotlinを使ってNull Safetyにしてあげましょう。

import java.util.*

class Chocolate
class Kanojo {
    var choco: Chocolate = Chocolate()
        private set
}

class Naotiki {
    var kanojo: Kanojo? = null
        private set

    fun eat(choco: Chocolate) {
        println("おいし~")
    }
}

fun main(args: Array<String>) {
    //私のインスタンスを生成
    val naotiki = Naotiki()
    val calendar = Calendar.getInstance()
    //バレンタインなら?
    if (calendar[Calendar.MONTH] == Calendar.FEBRUARY && calendar[Calendar.DAY_OF_MONTH] == 14) {
        //チョコがもらえる・・・?
        val choco = naotiki.kanojo.choco
        naotiki.eat(choco)
    }
}

とりあえずKotlinに変換してみました。
初期値がないといけないので今回はNullを代入しておきます。
併せて、KanojoはNull許容のKanojo?にします。
するとIntelliJくんがなにか言いたそうです。

image.png

Kanojo? の Null 許容レシーバーでは、安全な (?.) または非 Null アサート (!!.) 呼び出しのみが許可されます。

なるほど、Nullかもしれないものはそのまま呼び出せないんですね。
!!.を使うと先程と同じ結果になってしまうので安全な方の?.を使いましょう。

fun main(args: Array<String>) {
    //なおちきのインスタンスを生成
    val naotiki = Naotiki()
    val calendar = Calendar.getInstance()
    //バレンタインなら?
    if (calendar[Calendar.MONTH] == Calendar.FEBRUARY && calendar[Calendar.DAY_OF_MONTH] == 14) {
        //チョコがもらえる・・・?
-       val choco = naotiki.kanojo.choco
+       val choco = naotiki.kanojo?.choco
        naotiki.eat(choco)
    }
}

ん?
また何かIntelliJが言いたそうです。

image.png

kanojoがNullかもしれないので?.でアクセスしたchocoもNullかもしれない型になるんですね!
Null許容チョコはeat()出来ないので怒られていますね。
?.let { ... }をうまく使って書いてみましょう。

fun main(args: Array<String>) {
    //なおちきのインスタンスを生成
    val naotiki = Naotiki()
    val calendar = Calendar.getInstance()
    //バレンタインなら?
    if (calendar[Calendar.MONTH] == Calendar.FEBRUARY && calendar[Calendar.DAY_OF_MONTH] == 14) {
        //チョコがもらえる・・・?
        val choco = naotiki.kanojo?.choco
-       naotiki.eat(choco)
+       naotiki.kanojo?.let { she ->
+           naotiki.eat(she.choco)
+       }
    }
}

kanojoがNullじゃないときにletが実行され、その中でNullではないことが保証されたkanojo(let内ではshe)から貰ったchocoeat()するんですね。
もしkanojoがNullなら何も実行されずにそのまま終了するようです。

直し終わったところで・・・
2回目の実行です!

java.exe (省略)

プロセスは終了コード 0 で終了しました

やりました!ぬるぽを回避し私に平穏な2/14が戻ってきました!

私の彼女がNullではない世界線の実行結果

java.exe (省略)
おいし~

プロセスは終了コード 0 で終了しました

おわり

久しぶりにQiita書きたくなったので書きました。
書いてて私は悲しくなりました。1

コメントで:chocolate_bar:(:chocolate_bar:)をくれると私が喜びます

KotlinのNull Safetyについて、詳しくはこちらを見てください

  1. Q. なにも行動しないお前が悪いのでは? A. その通りです。(泣)

48
12
13

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
48
12