LoginSignup
8
6

More than 5 years have passed since last update.

Optional の7つのルールについて考える

Posted at

概要

先日参加した Devoxx US 2017 というカンファレンスで、Optional を扱う際のルールについて知ったので、まとめるついでに思ったことを書いてみます。

Optional の7つのルール

Mr.Stuart Marks(@stuartmarks) の "Optional - The Mother of all Bikesheds" というセッションを聴講しました。そこで java.util.Optional を使うときの7つのルールが紹介されていました。

  1. Never, ever, use null for an Optional variable or return value.
  2. Never use Optional.get() unless you can prove that the Optional is present.
  3. Prefer alternatives to Optioal.isPresent() and Optional.get().
  4. It’s generally a bad idea to create an Optional for the specific purpose of chaining methods from it to get a value.
  5. If an Optional chain has a nested Optional chain, or has an intermediate result of Optional, it’s probably too complex.
  6. Avoid using Optional in fields, method parameters, and collections.On a related note, I thought of another rule after I presented the session:
  7. Don’t use an Optional to wrap any collection type (List, Set, Map). Instead, use an empty collection to represent the absence of values.

それぞれがどういうことかは(Rule 7を除いて)後述の資料に記載がありますので、そちらをご覧ください。ちなみに present は「Optional に値が入っている状態(≒Not Empty)」、absent は「Optional に値が入っていない状態(≒Empty)」を示します。

資料

まだ Devoxx US 2017 版のは公開されていませんが、その前にも別のカンファレンスで同じタイトルのセッションをなさっていたようで、その資料と映像は公開されています。この資料、Optional を使い始めようと考えている人、最近 Optional を使い始めた人には非常に有益な情報が載っていますので、一読をお勧めします。

Format Link
資料 https://stuartmarks.files.wordpress.com/2016/09/optionalmotherofallbikesheds3.pdf
動画 https://www.youtube.com/watch?v=Ej0sss6cq14
Twitter のハッシュタグ https://twitter.com/hashtag/DevoxxOptional?src=hash

Optional の orElse Family

Optional に入っている値を取り出すには get() か orElse Family メソッドを使います。get() メソッドは Optional オブジェクトが empty のときに実行時例外 NoSuchElementException が出ますので、 isPresent で確認しない限りは使ってはいけないとのことです。通常は orElse Family メソッド、ないし ifPresent(present の時に実施する処理を Lambda 式で記述するメソッド) を使うことになるでしょう。

Method Do absent
orElse(DEFAULT_VALUE) DEFAULT_VALUE を返す(もしここにメソッドを指定した場合、 present でも absent でも実行される)
orElseGet(Object::new) Supplier.get が呼ばれ、そこで生成されたオブジェクトが返される
orElseThrow(Exception::new) 例外を投げる

積極的に Optional.orElseGet を使いたいケース

ちょっと前に TL で話題になっていました。(参考:Optionalから値を取り出すにはorElseGet()を使う) absent な時に返す値をメソッド呼び出しで用意する場合は、積極的に orElseGet を使った方がよいでしょう。こちらだと absent の時だけメソッドが呼ばれます。

orElse

この行が実行されるときは必ずCollections.emptyList()が呼ばれる
.orElse(Collections.emptyList())

orElseGet

absentの時だけCollections.emptyList()が呼ばれる
.orElseGet(Collections::emptyList)

Optional.get()

これまで「Optional.get() は Optional の価値を否定するものだから削除すべき」と個人的に思っていました。が、実際はそう簡単な話でもないとわかりました。まず下記のコードをご覧ください。

isPresent&get
final Optional<Bounds> caretBoundsOr = editor.getCaretBounds();
if (caretBoundsOr.isPresent()) {
    final Bounds bounds = caretBoundsOr.get();
    return new Point2D(bounds.getMinX(), bounds.getMinY() + 20);
}
return null;

「isPresent でチェックしてから get する」というルールに則った処理です。 absent のケースでは return null; しているのが、「null 返すなら何で Optional 使っているの」と突っ込みたくなるケースです。

では、何で使ったのか?

  • ライブラリの API が Optional でない生の値を返すメソッドを用意していなかった…… RichTextFX というライブラリの CodeArea.getCaretBounds メソッドが Optional を返してくるので、使わざるを得ませんでした。
  • オブジェクト生成がライブラリ使用者からは呼び出しにくい設計となっていたため、 orElse Family のメソッドを使えなかった

前述の通り、一応 isPresent() でチェックしてから値を取り出しているので、Mr.Marks のルールには則っています。が、Optional の isPresent -> get はただの null チェックをするコードよりも良いとは言い難いです。

ただの null チェックをするコードに Optional が劣る点

  1. Optional がオブジェクトであり、単純な null チェックでは使う必要のない Optional オブジェクトを1つ余計に生成している
  2. Optional を使ったコードにすると、デバッガでの細かいステップ実行ができなくなる
  3. Java 8 以降でないと Optional が利用できない

修正案

考えてみたところ、無難なのはこんな書き方でしょうか……

final Optional<Point2D> pointOr
        = editor.getCaretBounds()
                .map(bounds -> new Point2D(bounds.getMinX(), bounds.getMinY() + 20));
return pointOr.isPresent() ? pointOr.get() : null;

Optional は null チェックだけでなく、その後の処理も一緒に書くようなケースで filter(値が条件に一致する場合のみ次の operator に処理を続ける) や map(値を変換する) と一緒に用いた方が良いのではないかと思います。


安易な Optional の活用よりも三項演算子& null チェックが適するケース

Mr.Stuart Marks は下記のような Optional を使ったコードを書くなら、シンプルに三項演算子を使え、ということを仰りたかったのではないかと思いました。

アレなOptional.orElseのコード
Optional.ofNullable(runScript()).orElse("0").toString()

三項演算子を使った方が短い上にわかりやすく、そして無駄な Optional オブジェクトも作らなくて済みます。

同じことができる三項演算子のコード
final String script = runScript();
script != null ? script : "0"

参考

8
6
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
8
6