`get()`を使うな ~ 敗北者の Optional

Java の Optional に関する記事を見ると “この教え方では Optional の意味がまるっきりない使い方するやつばかりが生まれるぞ……” というのを数多く見てしまったので,せめてもの反抗にこの記事を書きます。

TL; DR

isPresent()get()しか知らない奴は

yamero.png

こういう書き方をしてはいけない

記事を見ていると

null チェックはOptional#isPresent()で行い,値はOptional#get()で取り出します。

みたいな解説が多すぎる。

そういうことをしてはいけない。それでは従来の null チェックとなにも変わらない。変わらないどころかタイプ数が増え,実行時間が増え,メモリ使用量が増え,デメリットだけが上乗せされている。メリットはなにも得られていないのに。

Optional<String> someStr = Optional.of("Hello, world!");

if (someStr.isPresent()) {
    System.out.println(someStr.get()); // やってはいけない
}

まず頭に叩き込んでほしいのはOptional#get()を使ってはいけないということだ。

大事なことなのでもう一度言う。

Optional#get()は使うな

Optional#get()それが null かどうかを気にせずに取り出すウンコみたいなメソッドだ。Unsafe なのだ。“どーしてもそれを使わないといけないとき” のためだけに用意されている。

“直前に null チェックしてるからいいじゃん” とか抜かすアホは Antony Hoare にケツをファックされてからこの記事に戻ってくるように。“null チェックしてるからいい” なら従来の方法で十分なのだ。無意味だ。お前が null チェックを忘れようがOptional#get()は使える。そしてクラッシュする。バカバカしい話だ。

ではどうするか。Optional#ifPresent()を使う。このメソッドはメソッド参照を引数に取る。ので,ラムダ式を中に記述していくが……ラムダ式もメソッド参照も知らなくてもそれっぽく書けばいい

Optional<String> someStr = Optional.of("Hello, world!");

someStr.ifPresent(x -> {
    System.out.println(x);
});

Optional#ifPresent()に渡されたメソッド参照は null でないときだけ実行されるx一時的に使える変数だと思っていい。もちろん名前は (かぶらない限りは) 自由だ。型は当然Optional<T>に対してTとなる。ラムダ式やメソッド参照について知っておくのが一番だが,知らなくても通常の if 文と見た目は似ているので十分のはずだ。

null でないときだけ実行したいのではなく,値の有無で動作を分けたい場合もあるかもしれない。

Optional<String> someStr = Optional.empty();

if (someStr.isPresent()) {
    System.out.println(someStr.get()); // もちろんやってはいけない
} else {
    System.out.println("値はなかったよ");
}

そういうときはOptional#ifPresentOrElse()を使用する

Optional<String> someStr = Optional.empty();

someStr.ifPresentOrElse(x -> {
    System.out.println(x);
}, () -> {
    System.out.println("値はなかったよ");
});

() -> ...は引数を持たないラムダ式を意味するが,もちろん“値を使わないときのおまじない”だと思ってくれても構わない。

なお,Optional#ifPresentOrElse()は Java 9 で実装されたので,Java 8 の環境では

Optional<String> someStr = Optional.empty();

someStr.ifPresent(x -> {
    System.out.println(x);
}); if (!someStr.isPresent()) {
    System.out.println("値はなかったよ");
}

とするしかないだろう。

なるべくOptional#ifPresent()も使うな

できればOptional#ifPresent()も濫用すべきではないOptional#map()Optional#flatMap()Optional#orElse()で十分なときが多いはずだ。こちらの記事などを参照。Optional の世界では null チェックなんてしないのが基本なのだ。

敗北者が多すぎて敗北者向けの記事が書かれる始末

敗北者を生む自称解説記事が多いためにOptional#get()はそもそも使わないとか,null チェック自体しないのが理想という基本の基本部分があまり知られていないので “Optional などないほうがマシである” というようなトンチンカンな記事が書かれてしまうことになる。

  • NullPointerExceptionNoSuchElementExceptionにすり替えます。プログラムがクラッシュすることには変わりありません。

もし、myOPointが実際の座標を保持していない場合、myOPoint.get().xNoSuchElementExceptionを投げてプログラムをクラッシュさせます。これは元のコードから何一つ良くなってはいません。なぜなら、プログラマの目的はすべてのクラッシュを回避することであって、NullPointerExceptionによるクラッシュだけを回避できれば良いわけではないからです。

繰り返しになりますが、コードはどちらも似たようなもので、Optionalが従来の参照方法よりも勝っているとは言えません。

soreha.jpg

まあ,この記事の筆者もたぶん敗北者が多すぎて呆れてこういう記事を書いたのだろう。それでもこいつ物知らねえなという記述は散見されるけど。

Optional#get()を使わざるを得ないかもしれない例

仕様の関係で例外が発生しうる処理を外側でハンドリングすることはできない

public static int half(int even) throws Exception {
    if (even % 2 == 0); else {
        throw new Exception();
    }

    return even / 2;
}

/* ... */

Optional<Integer> someInt = Optional.of(16);

/* できる */
try {
    if (someInt.isPresent()) {
        System.out.println(half(someInt.get()));
    }
} catch (Exception e) {
    System.out.println("偶数を渡してください");
}

/* できない */
try {
    someInt.ifPresent(x -> {
        System.out.println(half(x));
    });
} catch (Exception e) {
    System.out.println("偶数を渡してください");
}

これをなんとかするために Qiita でも様々なハックが紹介されている。Either を使うことでもなんとかできると思う。モナドばんじゃい。まあ Java 公式に Either ないんですけどね。

BlueRayi
アマグラマ。大学では化け学を専攻。
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした