M.Hiroi's Home Page

続・お気楽 Java プログラミング入門

Optioal<T>


Copyright (C) 2016-2021 Makoto Hiroi
All rights reserved.

はじめに

Optioal<T> は Java 8 から導入されたクラスです。簡単に説明すると、値をひとつ保持しているか、もしくは値が無いことを表すクラスです。他のプログラミング言語では、SML/NJ や OCaml などの Option 型、Haskell の Maybe、Scala の Option クラスに相当する機能です。

たとえば、データを探索するメソッドで、データが見つからなかったときに null を返すよりも、Optional<T> を使ったほうが安全なプログラムを作ることができます。また、ジェネリクスのほかにも int, long, double 専用の Optional も用意されています。

●Optional<T> の生成

Optional<T> のインスタンスはスタティックメソッドで生成します。

  1. Optional.of(T e)
  2. Optional.ofNullable(T e)
  3. Optional.empty()

1 は引数 e を格納したインスタンスを生成します。null 以外の値であることが確実な場合に使います。2 は引数 e が null の場合は値がないことを表すインスタンス (empty) を、そうでなければ値を格納したインスタンスを生成します。3 は文字通りの意味で、empty を生成します。

簡単な例を示しましょう。

jshell> Optional.of(123)
$1 ==> Optional[123]

jshell> Optional.of(1.2345)
$2 ==> Optional[1.2345]

jshell> Optional.ofNullable(456)
$3 ==> Optional[456]

jshell> Optional.ofNullable(null)
$4 ==> Optional.empty

jshell> Optional.empty()
$5 ==> Optional.empty

●Optional<T> のメソッド

値の有無はメソッド isPresent() で調べることができます。値は get() で取得することができますが、empty に get() を適用すると NoSuchElementException という例外が送出されます。これでは null と変わらないので不便ですね。値を取得する、もしくはメソッドに渡したい場合は、次のメソッドを使うと便利です。

簡単な例を示しましょう。

jshell> var a = Optional.of(123)
a ==> Optional[123]

jshell> a.orElse(0)
$7 ==> 123

jshell> a.ifPresent(System.out::println)
123

jshell> var b = Optional.empty()
b ==> Optional.empty

jshell> b.orElse(0)
$11 ==> 0

jshell> b.ifPresent(System.out::println)

最後の ifPresent() は Optional に値がないので、println() は実行されません。

●Optional<T> の高階関数

Optional のデータを処理する場合、その結果を Optional に包んで返したい場合があります。このような場合、いちいちデータを取り出すのは面倒なので、Optional のままデータを処理できると便利です。Optional<T> には、このような処理に適した高階関数が用意されています。

flatMap() の使い方がちょっと難しいかもしれません。たとえば、map() の引数に map() を渡すことを考えてみましょう。この場合、引数の map() の返り値は Optional になり、それを Optional に包んで返すので、返り値のデータ型は Optional<Optional<T>> になってしまいます。つまり Optional が二重になってしまうのです。このような場合、flatMap() を使うと便利です。

Lisp などの関数型言語や論理型言語の Prolog など、リストを操作するプログラミング言語では、リストの中にリストを格納することができます。二重になったリストをフラットなリストに変換する操作を「平坦化 (flattening)」といいます。このような処理を行う関数は、多くの処理系で flatten という名前で定義されています。

簡単な例を示しましょう。

jshell> var a = Optional.of(1234)
a ==> Optional[1234]

jshell> a.filter(x -> x % 2 == 0)
$2 ==> Optional[1234]

jshell> a.filter(x -> x % 2 != 0)
$3 ==> Optional.empty

jshell> a.map(x -> x * x)
$4 ==> Optional[1522756]

jshell> var b = a.map(x -> x * x)
b ==> Optional[1522756]

jshell> a.flatMap(x -> b.map(y -> x + y))
$6 ==> Optional[1523990]

一番最後の例は、a または b が empty の場合でも結果は empty になります。このように、高階関数を使うと Optional のまま処理することができます。また、これらのメソッドをつないで処理した場合、Optional ならば途中で値が empty になったとしても、最終結果は empty になります。

つまり、Optional のメソッドチェインでは、途中で empty をチェックする必要がないのです。その分だけプログラムを簡単に記述することができます。Haskell の Maybe や Either のように、簡単なエラー処理に Optional を使うことができるかもしれません。興味のある方はいろいろ試してみてください。


初版 2016 年 12 月 4 日
改訂 2021 年 2 月 21 日