261
132

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-30

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#ifPresentOrElse()の例などはこれらのメソッドを使えばより簡潔に書ける。emptyだったときの代用値を指定してアンラップするOptional#orElse()を使用すれば,

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

System.out.println(someStr.orElse("値はなかったよ"));

これでよい。もしOptional<String>ではなくOptional<Integer>だったら? ならばemptyでないときだけ関数適用するOptional#map()の出番だ。

Optional<Integer> someInt = Optional.empty();

System.out.println(someInt.map(x -> x.toString()).orElse("値はなかったよ"));

あなたがメソッド参照について勉強すればさらに簡潔に書ける。

Optional<Integer> someInt = Optional.empty();

System.out.println(someInt.map(Object::toString).orElse("値はなかったよ"));

無論,一行が長いと思ったら外に変数を定義して引き出してもいい。

より多彩な例はこちらの記事などを参照。

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

敗北者を生む自称解説記事が多いために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 ないんですけどね。

261
132
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
261
132

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?