新設されたList生成メソッドを既存のメソッドの代わりに使う場合の注意点

Java9以降で、List生成メソッドがいくつか新設されましたが、既存のList生成メソッドを単純に置き換えると危険な場合があります。


## Listを生成するメソッド

Listを生成するメソッドで、よく使うものは以下があります。

```java
// Java1.2から
java.util.ArraysのasList(E...) 

// Java8から
java.util.Streamのcollect(java.util.Collectors.toList())

// Java9から
java.util.Listのof(E...) 

// Java16から
java.util.StreamのtoList()
```

このうち、`Stream.of(...).collect(Collectors.toList())`だけは可変リストになっています。

```java
|  JShellへようこそ -- バージョン17.0.9
|  概要については、次を入力してください: /help intro

jshell> var arraysAsList = Arrays.asList("ABC")
arraysAsList ==> [ABC]

jshell> arraysAsList.add("XYZ")
|  例外java.lang.UnsupportedOperationException
|        at AbstractList.add (AbstractList.java:153)
|        at AbstractList.add (AbstractList.java:111)
|        at (#2:1)

jshell> var collectorsToList = Stream.of("ABC").collect(Collectors.toList())
collectorsToList ==> [ABC]

jshell> collectorsToList.add("XYZ")
$1 ==> true

jshell> /vars collectorsToList
|    List<String> collectorsToList = [ABC, XYZ]

jshell> var listOf = List.of("ABC")
listOf ==> [ABC]

jshell> listOf.add("XYZ")
|  例外java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:142)
|        at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:147)
|        at (#6:1)

jshell> var streamToList = Stream.of("ABC").toList()
streamToList ==> [ABC]

jshell> streamToList.add("XYZ")
|  例外java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:142)
|        at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:147)
|        at (#8:1)

jshell>
```


## nullを受け付けないList.of()

もう１つ注意したほうが良さそうなのが、これです。`Arrays.asList()`は`null`を許容するので、場合によっては`Arrays.asList()`の代わりに使うとまずいことになります。

 * https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/List.html#unmodifiable
   インタフェースList(java.util.List) 変更不可能なリスト - Java17 API仕様
> それらはnull要素を許可しません。 null要素でそれらを作成しようとすると、NullPointerExceptionになります。 

これ以外にも注意点はありますが、今回はこの部分だけの紹介に留めます。


## List.of()に置き換えて失敗するケース（フィクション）

前述のとおり、`List.of()`は、`Arrays.asList()`と異なり`null`を受け付けません。そのため、場合によっては単純に`Arrays.asList()`の代わりに使えないかもしれません。

以下の例はフィクションですが、過去に似た事象（言うほど似てないかも）に遭遇したことはあったので、あり得ない話ではないかと思います。
ちなみに、`Stream.toList()`で作ったリストに`add()`するケースは、説明するまでもないですよね。

`MemberSearchApi`と`RegistrationApi`は、それぞれ別の部署が管轄するAPIで、`MemberSearchApi`はかなり昔に作られていて使い勝手が微妙なものとします。

```java
/**
 * メンバーを検索するAPI。
 */
public class MemberSearchApi {

	/**
	 * メンバーを検索する。複数指定可能。
	 * 
	 * @param id メンバーID （可変長）
	 * @return keyと同じサイズの配列、対応するメンバーが見つからなかった場合の要素は null
	 */
	public Member[] find(String... memberIds) {
		Member[] results = new Member[memberIds.length];
		for (int i = 0; i < memberIds.length; i++) {
			results[i] = find(memberIds[i]);
		}
		return results;
	}

	private String find(String keyword) {
		// 実物は、何らかのテーブルを検索(当時はMap.ofは使えなかったはずだがそこは無視）
		var table = Map.of("D0010001", Member.named("Alice"), "D0010003", Member.named("Brian"));
		return table.get(id);
	}
}
```


```java
/**
 * 登録API。
 */
public class RegistrationApi {

	/**
	 * 参加メンバーを登録する。
	 * 
	 * @param memberList メンバーリスト
	 */
	public void joinMembers(List<Member> memberList) {
		// 略
	}
}
```


この2つのAPIをリレーします。

`RegistrationApi`は、`null`を読み飛ばしてくれるので、`Arrays.asList()`であれば問題なく動作するようです。

```java
MemberSearchApi memberSearchApi = new MemberSearchApi();
Member[] members = memberSearchApi.find("D0010001", "D0010002", "D0010003");

RegistrationApi registrationApi = new RegistrationApi();
registrationApi.joinMembers(Arrays.asList(members));

// => 正常終了
```

片や、`List.of()`では・・・

```java
registrationApi.joinMembers(List.of(members));
```

```
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:208)
	at java.base/java.util.ImmutableCollections.listFromArray(ImmutableCollections.java:190)
	at java.base/java.util.List.of(List.java:1047)
	at ...

```


## おわり