Java9以降で、List生成メソッドがいくつか新設されましたが、既存のList生成メソッドを単純に置き換えると危険な場合があります。
Listを生成するメソッド
Listを生成するメソッドで、よく使うものは以下があります。
// 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())
だけは可変リストになっています。
| 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()
もう1つ注意したほうが良さそうなのが、これです。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
はかなり昔に作られていて使い勝手が微妙なものとします。
/**
* メンバーを検索する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);
}
}
/**
* 登録API。
*/
public class RegistrationApi {
/**
* 参加メンバーを登録する。
*
* @param memberList メンバーリスト
*/
public void joinMembers(List<Member> memberList) {
// 略
}
}
この2つのAPIをリレーします。
RegistrationApi
は、null
を読み飛ばしてくれるので、Arrays.asList()
であれば問題なく動作するようです。
MemberSearchApi memberSearchApi = new MemberSearchApi();
Member[] members = memberSearchApi.find("D0010001", "D0010002", "D0010003");
RegistrationApi registrationApi = new RegistrationApi();
registrationApi.joinMembers(Arrays.asList(members));
// => 正常終了
片や、List.of()
では・・・
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 ...