こまぶろ

技術のこととか仕事のこととか。

StreamとOptionalに共通して存在するメソッド(その2):of / ofNullable / empty

StreamとOptionalに共通して存在するメソッドの第二弾。 第一弾はこちら。

ky-yk-d.hatenablog.com

前回の記事では、filter()について書いた。今回は、of()ofNullable()について書……こうと思ったら実はempty()も共通して存在するメソッドだったことに気づいたので、これも併せて3つのメソッドについて書く。

ファクトリメソッド: of()ofNullableempty()

メソッド Stream Optional
of() static Stream of​(T t) public static Optional of​(T value))
ofNullable() static Stream ofNullable​(T t) public static Optional ofNullable​(T value)
empty() static Stream empty() public static Optional empty()

of()ofNullable()empty()は、StreamとOptionalのいずれにおいてもstaticのファクトリメソッドだ。Streamはクラスではなくインタフェースだが、Java 8からはインタフェースにstaticメソッドが書けるようになっているため*1、ファクトリメソッドを定義することができる。実際には、下記のように定義されている。

public static<T> Stream<T> of(T t) {
    return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}

public static<T> Stream<T> ofNullable(T t) {
    return t == null ? Stream.empty()
                     : StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}

public static<T> Stream<T> empty() {
    return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
}

対して、Optionalのそれぞれのメソッドの定義は下記の通りだ。

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

こうして並べてみると、StreamとOptionalのそれぞれにおいて、of()ofNullable()という2つのメソッドの関係が同じであることがよくわかる。of()が引数で受け取った何らかの値をそのままStreamないしOptionalに包んで「返そうとする」のに対して、ofNullable()は引数がnullだった場合は空のインスタンス(それぞれのempty()メソッドで取得する)を返し、nullでない場合はof()と同じ結果を返すようになっている。空のインスタンスについては、その挙動を含めて次回に回す。

of()にnullを渡した場合の挙動の差異

それでは、of()の引数にnullを渡した場合の挙動はどうなるのか。これはStreamとOptionalで異なっており、Streamのof()が、単に(nullが実体を持ったオブジェクトであるかのような表現には語弊があるが)「null 1つ」を要素とするStreamを返すのに対し、Optionalのof()NullPointerExceptionをスローする*2。下記のコードで実際に動作を確認できる。

/* JUnit5を利用 */
@Test
void Streamのofはnullを含むStreamを返す() {
    List<Object> list = Stream.of((Object)null).collect(Collectors.toList());
    assertEquals(1, list.size()); // 要素数は1
    assertNull(list.get(0)); // その要素はnull
}
@Test
void Optionalのofは例外をスローする() {
    final String helloWorld = "Hello World";
    Optional<Object> opt = Optional.of(helloWorld);
    Exception exception = null; // スローされたら格納しておく
    try {
        opt = Optional.of(null);
        fail("例外がスローされるので到達したらいけないコード");
    } catch (Exception e) {
        exception = e;
        assertEquals(NullPointerException.class, e.getClass());
    }
    assertEquals(helloWorld, opt.get()); // 当初の値のままであるはず
    assertNotNull(exception); // 例外がスローされていることを確認
}

その他の違い

以上のように、of()にnullを渡した際の挙動はStreamとOptionalとで異なっている。その他に、異なる点としては、

  • Optionalのof()には(Optionalの性質上)単一の引数のものしか存在しないのに対して、Streamのof()にはof​(T... values)という可変長引数のものも存在している。
  • これら6つのメソッドのうち、StreamのofNullable()だけは導入されたバージョンがJava 9であり、一世代新しい。

という点が挙げられる。

感想と次回予告

じっくりと仕様と実装を見てみると、普段いかに雰囲気で使ってしまっているかに気づくことができた。とはいえ、ファクトリはファクトリでしかないので、それぞれの生成の仕方にどのような意味があるのかは、生成が利用される場面とセットでなければわかりにくい。次の記事では、map()flatMap()を紹介しながら、今回の記事で扱ったファクトリをどのように利用するかにも言及したい。

*1:また、インスタンスメソッドについてはdefaultキーワードを用いてデフォルト実装を定義することもできるようになっている。

*2:スローするのはof()の中で呼び出しているOptionalのコンストラクタ、更に言えばそこで呼び出しているObjects.requireNonNull()だ。