JavaのコレクションファクトリーメソッドやGuavaのImmutableCollectionは、nullを要素に使えない

Java9で追加された List#of などのファクトリーメソッドや、Java10で追加された Collectors#toUnmodifiableList などのイミュータブルなコレクション生成をよく使っているが、nullを含んだ値を指定したら NullPointerException が発生した。

標準APIやGuavaのImmutableListなどでも、不変のコレクションを生成するメソッドでは要素にnullを設定できなかったのでメモ。

Java標準API

List

Java9で追加された List#of、Java10で追加された List#copyOfCollectors#toUnmodifiableList で、変更不能リストを生成する場合、要素はnull不可となる。

それぞれ List および CollectorsJavadocに記載あり。

List#of および List#copyOf については、Listのクラスに対するJavadocにも詳細がある。

Set

Listと同様、 Set#ofSet#copyOfCollectors#toUnmodifiableSet で変更不能セットを生成する場合、要素はnull不可となる。

これまた SetJavadocに記載あり、クラスに対するJavadocに詳細があるのも同様。

Map

こちらも Map#ofMap#copyOfCollectors#toUnmodifiableMap で変更不能マップを生成する場合、キーおよび値はnull不可となる。 MapJavadocに記載があるのも同じ。

Guava

ImmutableListImmutableSetImmutableMap など、 com.google.common.collect 内の Immutable で始まるクラスは、すべて要素やキーと値がnull不可となっている。

詳細は、ImmutableCollection に記載あり。 ImmutableMap は継承関係にないが、継承しているか否かにかかわらず、 Immutable で始まるクラスに適応されるとのこと。

The remainder of this documentation applies to every public Immutable- type in this package, whether it is a subtype of ImmutableCollection or not.

nullを含むコレクションを変更不可コレクションにするには

nullを要素に含んだListやSet、値に含んだMapを変更不可にするには、Optionalでラップするのが手っ取り早い。

ListやSetであれば、Streamにしてmapで変換すればいい。

// of の置き換え
Stream.of(...)
        .map(Optional::ofNullable)
        .collect(Collectors.toUnmodifiableList());

// copyOf の置き換え
values.stream()
        .map(Optional::ofNullable)
        .collect(Collectors.toUnmodifiableList());

Mapの場合、すでにコピー元のインスタンスが存在すれば、Guavaの Maps#transformValues を使うと簡単。

Map.copyOf(Maps.transformValues(map, Optional::ofNullable));

Map#of を使っていた場合、変更可能なマップを生成する同様のメソッドがないため、HashMapなど適当なMapインスタンスを生成した後に、 Maps#transformValues で変換か。

キーがnullのMapは、まず使わないので無視してよさそう。

振り返り

nullが入らないことで、nullチェックが不要となり、かなりシンプルに使えるようになるため、この仕様はありがたい。

ただ、ListやSet、Mapといったインタフェースで変数や戻り値を宣言すると、要素などがnull不可という情報が消えてしまう。Guavaが使える環境であれば、ImmutableCollectionのJavadocにあるように、ImmutableListなどを変数や戻り値に使っておくのがよさそう。

For field types and method return types, you should generally use the immutable type (such as ImmutableList) instead of the general collection interface type (such as List). This communicates to your callers all of the semantic guarantees listed above, which is almost always very useful information.