Arrays.asList、List.of、ArrayListでadd・set・nullの扱いが異なることを解説する記事のアイキャッチ。

Java

JavaのArrays.asListにaddできない原因|List.of・ArrayListとの違い

AIによる要約

Arrays.asListが返すListは配列をListとして見せる固定長のビューです。setによる要素の置換はできますが、addやremoveで要素数を変えるとUnsupportedOperationExceptionになります。List.ofは追加・削除・置換ができず、nullも許可しません。追加や削除が必要ならnew ArrayList<>(...)で可変リストを作ります。「List型だから変更できる」と判断せず、生成方法と所有者を確認することが重要です。

新人SE
新人SE
変数の型はListなのに、addしたらUnsupportedOperationExceptionになりました。なぜコンパイルで止まらないんですか?
ポンコツSE
ポンコツSE
Listはaddという操作を定義していますが、すべての実装が要素数の変更を許すわけではありません。どの方法で作ったListかを確認します。

Javaの業務システムでは、固定値、検索結果、CSV列、画面の選択肢などをListで扱います。見た目は同じ`List<String>`でも、`ArrayList`、`Arrays.asList`、`List.of`では変更できる範囲が異なります。

この違いを知らないと、開発環境では通った処理が特定データで突然`UnsupportedOperationException`になったり、共通定数を意図せず書き換えたりします。API名の暗記ではなく、要素数を変えるか、要素を置き換えるか、nullを許すかで判断しましょう。

この記事のポイント

  • Arrays.asListは固定長でadd・removeできない
  • Arrays.asListはsetでき、元配列と値が連動する
  • List.ofはadd・remove・setできずnullも許可しない
  • 変更が必要ならnew ArrayList<>でコピーする
  • 引数として受け取ったListを勝手に変更しない

Xのクイズ:Arrays.asListへaddできるか

Arrays.asListでAとBを持つListを作り、Cをaddできるかを問うJavaクイズ画像。
Arrays.asListで作ったListへCを追加すると、どうなるでしょうか。
List<String> names =
        Arrays.asList("A", "B");

names.add("C");
System.out.println(names);

`names.add("C")`で`UnsupportedOperationException`が発生します。`Arrays.asList`は`java.util.Arrays.ArrayList`という内部クラスを返しますが、通常使う`java.util.ArrayList`とは別物です。

元のクイズはXの出題ポスト回答ポストで確認できます。

Arrays.asListは配列をListとして扱う固定長ビュー

`Arrays.asList`が返すListは、渡した配列を背後に持ちます。配列の長さは作成後に変えられないため、List側でも要素数を増減できません。

String[] array = {"A", "B"};
List<String> list = Arrays.asList(array);

list.set(0, "X");
System.out.println(Arrays.toString(array)); // [X, B]

array[1] = "Y";
System.out.println(list); // [X, Y]

`set`による置換はできます。そしてListを変更すると元配列も変わり、元配列を変更するとListにも反映されます。コピーではなく、同じ要素を別の見せ方で扱っている点が重要です。

操作Arrays.asList
getできる
setできる
addできない
removeできない
null要素扱える
元配列との連動する

変更したいならArrayListへコピーする

List<String> names =
        new ArrayList<>(Arrays.asList("A", "B"));

names.add("C");
names.remove("A");

System.out.println(names); // [B, C]

`new ArrayList<>(...)`は要素を新しい可変リストへコピーします。元配列と切り離されるため、追加・削除・置換が可能です。

Java 9以降なら、初期値を`List.of`で表し、それをArrayListへコピーする書き方もできます。

List<String> names =
        new ArrayList<>(List.of("A", "B"));
names.add("C");

Xのクイズ:List.ofはnullを持てるか

List.ofへA、null、Cを渡した場合の実行結果を問うJavaクイズ画像。
List.ofへnullを渡すと、作成時点で例外になります。
List<String> values = List.of(
        "A", null, "C");

このコードは`NullPointerException`になります。List.ofは変更不可であるだけでなく、null要素も許可しません。元のクイズはXの出題ポスト回答ポストにあります。

List.ofは変更不可の値を表す

List<String> roles = List.of("USER", "ADMIN");

roles.add("AUDITOR");  // UnsupportedOperationException
roles.set(0, "GUEST"); // UnsupportedOperationException

List.ofで作ったListは、add・remove・setのすべてができません。「この一覧は作成後に変わらない」という意図をコードで表せます。権限種別や固定ステータスなど、共通定数を誤って変更されたくない場面に向いています。

ただし変数を`final`にするだけではListの中身は守れません。`final List<String> list = new ArrayList<>();`は、別のListを再代入できないだけで、`list.add`は可能です。中身の変更を防ぐにはList.ofや`List.copyOf`などを使います。

ArrayList・Arrays.asList・List.ofの違い

作り方add/removesetnull主な用途
new ArrayList<>()可能可能可能後から編集する一覧
Arrays.asList(array)不可可能可能既存配列をList APIで扱う
List.of(...)不可不可不可変更しない固定値
List.copyOf(list)不可不可不可受け取った一覧の変更不可コピー

「どれが新しいAPIか」ではなく、作成後に誰がどの操作をするのかで選びます。

現場で起きる典型的な不具合

固定値へ条件付き要素を追加する

List<String> columns = List.of("id", "name");

if (includeEmail) {
    columns.add("email");
}

テストで`includeEmail=false`しか通らなければ問題は見えません。本番でtrueになった時だけ例外になります。追加する可能性がある時点でArrayListを選びます。

List<String> columns =
        new ArrayList<>(List.of("id", "name"));

if (includeEmail) {
    columns.add("email");
}

引数として受け取ったListを直接変更する

void normalize(List<String> values) {
    values.removeIf(String::isBlank);
}

呼び出し元がList.ofを渡すと例外になります。また可変Listでも、呼び出し元が保持する一覧まで変わります。メソッド内だけで加工するならコピーを作ります。

List<String> normalize(List<String> values) {
    List<String> result = new ArrayList<>(values);
    result.removeIf(String::isBlank);
    return result;
}

Arrays.asListのsetで元配列まで変える

外部ライブラリへ渡す配列をArrays.asListでラップし、画面表示用にsetした結果、後続の送信データまで変わることがあります。元配列を変更してよいか判断できない場合は、`new ArrayList<>(Arrays.asList(array))`で分離します。

テストでは例外になる操作も確認する

@Test
void Arrays_asListには追加できない() {
    List<String> values = Arrays.asList("A", "B");

    assertThrows(
            UnsupportedOperationException.class,
            () -> values.add("C"));
}

@Test
void List_ofはnullを許可しない() {
    assertThrows(
            NullPointerException.class,
            () -> List.of("A", null));
}

@Test
void ArrayListへコピーすれば追加できる() {
    List<String> values =
            new ArrayList<>(List.of("A", "B"));

    values.add("C");

    assertEquals(List.of("A", "B", "C"), values);
}

現場レビューでよくある指摘

// レビューコメント例
Arrays.asListの戻り値は固定長なのでaddできません。
追加が必要ならArrayListへコピーしてください。

// レビューコメント例
この一覧は固定値として扱うため、可変なArrayListではなく
List.ofで変更不可にすると意図が伝わります。

// レビューコメント例
引数のListを直接変更すると呼び出し元へ副作用が出ます。
メソッド内でコピーしてから加工してください。

提出前のセルフチェック

  • ListをどのAPIで作ったか確認したか
  • add・remove・setのどれが必要か整理したか
  • Arrays.asListと元配列が連動してもよいか
  • List.ofへnullが入る可能性はないか
  • 変更が必要ならArrayListへコピーしたか
  • 変更不要ならList.ofやList.copyOfを検討したか
  • 引数として受け取ったListを直接変更していないか
  • 条件分岐のtrue側もテストしたか

Javaコレクションを基礎から学ぶ参考書

List、Set、Mapの生成方法と変更操作を体系的に整理しておくと、実装クラスによる違いをレビュー前に判断しやすくなります。

書籍「スッキリわかるJava入門 第5版」の表紙
商品画像:Amazon.co.jp
PR Java初学者向け

スッキリわかるJava入門 第5版

中山 清喬 (著), 国本 大悟 (著), 株式会社フレアリンク (監修)

Java文法とオブジェクト指向を、最初から整理する一冊。

豊富な図とサンプルで、変数、条件分岐、クラス、例外処理まで順番に学べます。Javaの最初の教科書を探している人向けです。

  • Javaを基礎から体系的に学びたい
  • Java 25 LTS対応の入門書を選びたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

Arrays.asListは配列をListとして扱う固定長ビューなので、setはできますがadd・removeはできません。元配列と値が連動することにも注意が必要です。

List.ofは追加・削除・置換ができず、nullも許可しません。変更が必要ならArrayList、変更しないならList.of、配列との橋渡しならArrays.asListというように、作成後の操作と所有者から選びましょう。

-Java
-, ,