- JAVA
Java List.ofとArrays.asListの違い|変更可否の比較表
List.of と Arrays.asList の違いは、List.of が「要素の追加・削除・変更がすべてできず null も入らない不変リスト」、Arrays.asList が「要素の変更(set)はできるが追加・削除はできず、元の配列と中身がつながった固定長リスト」である点です。 どちらも add を呼ぶと UnsupportedOperationException になります。
この記事では、この2つに List.copyOf・Collections.unmodifiableList・Stream.toList・Collectors.toList・new ArrayList を加えた 7種類のリストを、add/remove/set/null/元配列・元リストとの連動まで ○× の比較表 にまとめました。表の ○× はすべて OpenJDK 21.0.10 で実行して確認 したもので、UnsupportedOperationException のスタックトレース全文、Arrays.asList(int[]) が List<int[]> になる罠、Set.of/Map.of の重複エラーも実行結果つきで紹介します。
List・Set・Map の使い分けそのものは既存記事「Java List Set Map 使い分け|選び方フローと実測つき」で扱っています。本記事は リストの作り方による「変更できる/できない」の違い に絞ります。
List.of・Arrays.asList など7種の早見表
迷ったら「あとで要素を増やす → new ArrayList<>(...)」「変えない定数 → List.of」「受け取ったリストを固定して保持 → List.copyOf」 と覚えると大半の場面で困りません。
| 作り方 | add / remove | set | null要素 | contains(null) | 元配列・元リストの変更が反映 | 追加された版 |
|---|---|---|---|---|---|---|
List.of(...) |
× | × | ×(NPE) | ×(NPE) | 反映しない(コピー) | Java 9 |
Arrays.asList(...) |
× | ○ | ○ | ○(false) | 反映する(配列と共有) | Java 1.2 |
List.copyOf(coll) |
× | × | ×(NPE) | ×(NPE) | 反映しない(コピー) | Java 10 |
Collections.unmodifiableList(list) |
× | × | ○ | ○(false) | 反映する(ビュー) | Java 1.2 |
stream.toList() |
× | × | ○ | ○(false) | 反映しない | Java 16 |
collect(Collectors.toList()) |
○(※) | ○(※) | ○ | ○(false) | 反映しない | Java 8 |
new ArrayList<>(...) |
○ | ○ | ○ | ○(false) | 反映しない(コピー) | Java 1.2 |
※ Collectors.toList() は Java 21 の実行では java.util.ArrayList が返り変更できましたが、Javadoc では「返す List の型・可変性は保証しない」とされています。変更する前提なら Collectors.toCollection(ArrayList::new) を使うと意図が明確です。
| やりたいこと | おすすめ |
|---|---|
| 定数や固定の選択肢を持つ | List.of("A", "B") |
| あとで add / remove する | new ArrayList<>(List.of(...)) |
| 配列をリストとして扱い、配列も一緒に書き換えたい | Arrays.asList(arr) |
| 引数で受け取ったリストを、呼び出し元の変更から守って保持 | List.copyOf(list) |
| 内部の可変リストを、読み取り専用で外に見せる | Collections.unmodifiableList(list) |
| Stream の結果を変更しない | stream.toList() |
| Stream の結果を変更する | collect(Collectors.toCollection(ArrayList::new)) |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java List.ofとArrays.asListの違い|変更可否の比較表」
7種類のリストを実行して比べる
早見表の add / remove / set / null の列は、次のコードで確認しました。それぞれの作り方でリストを作り、操作が成功すれば「○」、例外なら「×(例外名)」を出します。
// Compare.java(実行: java Compare.java)
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class Compare {
static String t(Runnable r) {
try { r.run(); return "○"; }
catch (Exception e) { return "×(" + e.getClass().getSimpleName() + ")"; }
}
static String tb(Supplier<Boolean> s) {
try { return "○(" + s.get() + ")"; }
catch (Exception e) { return "×(" + e.getClass().getSimpleName() + ")"; }
}
public static void main(String[] args) {
Map<String, Supplier<List<String>>> makers = new LinkedHashMap<>();
makers.put("List.of", () -> List.of("a", "b", "c"));
makers.put("Arrays.asList", () -> Arrays.asList("a", "b", "c"));
makers.put("List.copyOf", () -> List.copyOf(new ArrayList<>(List.of("a", "b", "c"))));
makers.put("Collections.unmodifiableList", () -> Collections.unmodifiableList(new ArrayList<>(List.of("a", "b", "c"))));
makers.put("Stream.toList", () -> Stream.of("a", "b", "c").toList());
makers.put("Collectors.toList", () -> Stream.of("a", "b", "c").collect(Collectors.toList()));
makers.put("new ArrayList", () -> new ArrayList<>(List.of("a", "b", "c")));
System.out.println("種類 | クラス | add | remove | set | sort | null要素で作成 | contains(null)");
for (var e : makers.entrySet()) {
String name = e.getKey();
List<String> cls = e.getValue().get();
String add = t(() -> e.getValue().get().add("d"));
String remove = t(() -> e.getValue().get().remove(0));
String set = t(() -> e.getValue().get().set(0, "z"));
String sort = t(() -> e.getValue().get().sort(Comparator.reverseOrder()));
String nul = switch (name) {
case "List.of" -> t(() -> List.of("a", null));
case "Arrays.asList" -> t(() -> Arrays.asList("a", null));
case "List.copyOf" -> t(() -> List.copyOf(Arrays.asList("a", null)));
case "Collections.unmodifiableList" -> t(() -> Collections.unmodifiableList(Arrays.asList("a", null)));
case "Stream.toList" -> t(() -> Stream.of("a", null).toList());
case "Collectors.toList" -> t(() -> Stream.of("a", null).collect(Collectors.toList()));
default -> t(() -> new ArrayList<>(Arrays.asList("a", null)));
};
String cn = tb(() -> e.getValue().get().contains(null));
System.out.println(name + " | " + cls.getClass().getName() + " | " + add + " | " + remove + " | " + set + " | " + sort + " | " + nul + " | " + cn);
}
}
}
実行結果(OpenJDK 21.0.10):
種類 | クラス | add | remove | set | sort | null要素で作成 | contains(null)
List.of | java.util.ImmutableCollections$ListN | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(NullPointerException) | ×(NullPointerException)
Arrays.asList | java.util.Arrays$ArrayList | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ○ | ○ | ○ | ○(false)
List.copyOf | java.util.ImmutableCollections$ListN | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(NullPointerException) | ×(NullPointerException)
Collections.unmodifiableList | java.util.Collections$UnmodifiableRandomAccessList | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ○ | ○(false)
Stream.toList | java.util.ImmutableCollections$ListN | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ×(UnsupportedOperationException) | ○ | ○(false)
Collectors.toList | java.util.ArrayList | ○ | ○ | ○ | ○ | ○ | ○(false)
new ArrayList | java.util.ArrayList | ○ | ○ | ○ | ○ | ○ | ○(false)
結果から読み取れるポイントです。
Arrays.asListはsetとsortができます。 要素の「入れ替え」はできても、「数(長さ)を変える」add/removeはできません。中身は配列なので長さが固定、と考えるとわかりやすくなります。List.of/List.copyOfはcontains(null)でもNullPointerExceptionになります。 null を「探す」だけでも例外になるのが意外な点です。indexOf(null)も同じく NPE でした(下の Linked.java で確認)。Stream.toList()は不変ですが null 要素を許可します。List.ofと同じ内部クラス(ImmutableCollections$ListN)でも、null の扱いが違います。Collections.unmodifiableListは内部クラス名がUnmodifiable...の「ラッパー」です。中身は元のリストそのものです(後述)。
なぜ List.of は null を拒否するのか
List.of などの不変コレクションは、Javadoc で「null 要素を許可しない」と定められています。 作成時の NullPointerException に加えて、contains(null)/indexOf(null) でも NPE になります。null を「値がない」意味で使いたいときは、リストに入れるのではなく Optional や空リストで表すと扱いやすくなります(Optional は既存記事「Java Optionalの使い方」を参照)。
UnsupportedOperationException の原因と直し方
UnsupportedOperationException は「このリストはその操作に対応していません」という実行時例外です。 コンパイルは通るため、実行して初めて気づきます。
List.of に add したときのスタックトレース全文
// Trace.java
import java.util.*;
public class Trace {
public static void main(String[] args) {
List<String> items = List.of("ペン", "ノート");
items.add("消しゴム");
}
}
実行結果(OpenJDK 21.0.10):
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
at Trace.main(Trace.java:7)
Arrays.asList に add したときのスタックトレース全文
// Trace2.java
import java.util.*;
public class Trace2 {
public static void main(String[] args) {
List<String> items = Arrays.asList("ペン", "ノート");
items.add("消しゴム");
}
}
実行結果(OpenJDK 21.0.10):
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.AbstractList.add(AbstractList.java:155)
at java.base/java.util.AbstractList.add(AbstractList.java:113)
at Trace2.main(Trace2.java:7)
スタックトレースの上の行を見ると、どの作り方のリストかがわかります。
| トレースに出るクラス | 作り方 | 例外メッセージ |
|---|---|---|
ImmutableCollections.uoe |
List.of/List.copyOf/Stream.toList |
なし(null) |
AbstractList.add/AbstractList.remove |
Arrays.asList |
なし(null) |
Collections$UnmodifiableCollection.add |
Collections.unmodifiableList |
なし(null) |
(3行目は Collections.unmodifiableList(new ArrayList<>(List.of("ペン"))) に add して確認した結果で、トレースの先頭は at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1092) でした。)
例外メッセージが空なので、e.getMessage() だけをログに出すと原因がわかりません。 ログには例外オブジェクトごと(スタックトレースつき)出すと、上の表のように作り方を特定できます。自分のコード(Trace.main(Trace.java:7))の行番号から、どの変数への操作かを確認します。
直し方:new ArrayList<>(...) で可変リストにする
要素を増減したいなら、new ArrayList<>(元のリスト) で可変のコピーを作ります。
// Fix.java
import java.util.*;
public class Fix {
public static void main(String[] args) {
List<String> items = new ArrayList<>(List.of("ペン", "ノート"));
items.add("消しゴム");
items.remove("ペン");
System.out.println(items);
List<String> fromArray = new ArrayList<>(Arrays.asList("A", "B"));
fromArray.add("C");
System.out.println(fromArray);
}
}
実行結果(OpenJDK 21.0.10):
[ノート, 消しゴム]
[A, B, C]
| 状況 | 直す前 | 直した後 |
|---|---|---|
| 初期値つきで後から追加 | List.of("a") |
new ArrayList<>(List.of("a")) |
| 配列から作って後から追加 | Arrays.asList(arr) |
new ArrayList<>(Arrays.asList(arr)) |
| Stream の結果に追加 | stream.toList() |
stream.collect(Collectors.toCollection(ArrayList::new)) |
| 受け取ったリストに追加(呼び出し元が不変かも) | param.add(x) |
var copy = new ArrayList<>(param); copy.add(x); |
| メソッドの戻り値に追加 | 戻り値をそのまま変更 | 戻り値を変更しない設計にするか、コピーしてから変更 |
「受け取ったリストを直接変更しない」ことを習慣にすると、相手がどの作り方でリストを渡してきても安全です。
元配列・元リストとの連動を確認する
Arrays.asList は元の配列と、Collections.unmodifiableList は元のリストと中身を共有します。 一方 List.of・List.copyOf・Stream.toList・new ArrayList はコピーなので連動しません。
// Linked.java
import java.util.*;
public class Linked {
public static void main(String[] args) {
// 元配列との連動
String[] arr = {"a", "b", "c"};
List<String> asList = Arrays.asList(arr);
List<String> of = List.of(arr);
arr[0] = "X";
System.out.println("配列を変更 -> asList=" + asList + " / List.of=" + of);
asList.set(1, "Y");
System.out.println("asList.set -> 配列=" + Arrays.toString(arr));
// 元リストとの連動
List<String> src = new ArrayList<>(List.of("a", "b"));
List<String> unmod = Collections.unmodifiableList(src);
List<String> copy = List.copyOf(src);
List<String> arrayCopy = new ArrayList<>(src);
List<String> streamed = src.stream().toList();
src.add("c");
src.set(0, "A");
System.out.println("元リストを変更 -> src=" + src);
System.out.println(" unmodifiableList=" + unmod);
System.out.println(" List.copyOf =" + copy);
System.out.println(" new ArrayList =" + arrayCopy);
System.out.println(" Stream.toList =" + streamed);
// copyOf は不変リストならコピーしない
List<String> immutable = List.of("a");
System.out.println("List.copyOf(List.of) 同一インスタンス? " + (List.copyOf(immutable) == immutable));
System.out.println("List.copyOf(unmodifiableList) 同一インスタンス? " + (List.copyOf(unmod) == unmod));
System.out.println("unmodifiableList(unmodifiableList) 同一インスタンス? " + (Collections.unmodifiableList(unmod) == unmod));
// indexOf(null)
try { List.of("a").indexOf(null); System.out.println("List.of.indexOf(null) OK"); }
catch (Exception e) { System.out.println("List.of.indexOf(null) -> " + e); }
System.out.println("Arrays.asList.indexOf(null) -> " + Arrays.asList("a").indexOf(null));
// equals
System.out.println("List.of(a,b).equals(Arrays.asList(a,b)) = " + List.of("a","b").equals(Arrays.asList("a","b")));
System.out.println("List.of(a,b).equals(new ArrayList) = " + List.of("a","b").equals(new ArrayList<>(List.of("a","b"))));
}
}
実行結果(OpenJDK 21.0.10):
配列を変更 -> asList=[X, b, c] / List.of=[a, b, c]
asList.set -> 配列=[X, Y, c]
元リストを変更 -> src=[A, b, c]
unmodifiableList=[A, b, c]
List.copyOf =[a, b]
new ArrayList =[a, b]
Stream.toList =[a, b]
List.copyOf(List.of) 同一インスタンス? true
List.copyOf(unmodifiableList) 同一インスタンス? false
unmodifiableList(unmodifiableList) 同一インスタンス? true
List.of.indexOf(null) -> java.lang.NullPointerException
Arrays.asList.indexOf(null) -> -1
List.of(a,b).equals(Arrays.asList(a,b)) = true
List.of(a,b).equals(new ArrayList) = true
Arrays.asList は配列と双方向につながる
配列を書き換えるとリストに、リストを set すると配列に反映されました。 Arrays.asList は配列をコピーせず、配列を「リストの形で見る」窓のようなものだからです。配列を別の用途で書き換える予定があるなら、List.of(arr) か new ArrayList<>(Arrays.asList(arr)) でコピーしておきます。
unmodifiableList は「読み取り専用のビュー」
Collections.unmodifiableList が防ぐのは「このラッパー経由の変更」だけです。 元の src に add/set すると、unmod から見える内容も [A, b, c] に変わりました。不変(中身が二度と変わらない)ではなく、読み取り専用の窓と考えます。
flowchart LR
SRC["src(ArrayList)<br>add・set できる"] --> UNMOD["unmodifiableList(src)<br>読み取り専用の窓"]
SRC -. コピー .-> COPY["List.copyOf(src)<br>独立した不変リスト"]
UNMOD -->|"add / set"| UOE["UnsupportedOperationException"]
SRC -->|"add / set"| UNMOD
| 目的 | 選ぶもの | 理由 |
|---|---|---|
| クラス内部で更新し続けるリストを、getter で読み取り専用で返す | Collections.unmodifiableList |
コピーせず軽い。最新の状態が見える |
| ある時点の内容を固定して保持(コンストラクタで受け取る等) | List.copyOf |
元が変わっても影響しない |
| 返した後の状態変化を見せたくない | List.copyOf |
スナップショットになる |
List.copyOf はコピーを省略することがある
List.copyOf(List.of(...)) は同じインスタンスを返しました(true)。 元がすでに不変リストならコピーは不要だからです。一方、unmodifiableList は中身が変わりうるため、List.copyOf は新しく作りました(false)。どちらの場合も「受け取った後に中身が変わらない」ことは同じなので、防御的コピーには List.copyOf を使えば、余分なコピーも自動で避けられます。
equals は作り方に関係なく中身で比べる
List.of(a,b).equals(Arrays.asList(a,b)) は true でした。 List の equals は「同じ順で同じ要素か」で判定する決まりなので、作り方が違っても中身が同じなら等しくなります。テストで比較するときに作り方を揃える必要はありません。
Arrays.asList(int[]) が List になる罠
Arrays.asList に int[] のようなプリミティブ型の配列を渡すと、「int[] を1個だけ持つリスト」になります。 可変長引数 T... の T にプリミティブ型は入れないため、配列全体が1つの要素として扱われるからです。
// IntArr.java
import java.util.*;
import java.util.stream.*;
public class IntArr {
public static void main(String[] args) {
int[] nums = {3, 1, 2};
List<int[]> wrong = Arrays.asList(nums);
System.out.println("Arrays.asList(int[]).size() = " + wrong.size());
System.out.println("中身 = " + wrong.get(0).getClass().getSimpleName() + " " + Arrays.toString(wrong.get(0)));
System.out.println("contains(3)? " + Arrays.asList(nums).contains(3));
List<int[]> wrong2 = List.of(nums);
System.out.println("List.of(int[]).size() = " + wrong2.size());
Integer[] boxed = {3, 1, 2};
System.out.println("Arrays.asList(Integer[]) = " + Arrays.asList(boxed) + " size=" + Arrays.asList(boxed).size());
List<Integer> ok1 = Arrays.stream(nums).boxed().toList();
List<Integer> ok2 = IntStream.of(nums).boxed().collect(Collectors.toCollection(ArrayList::new));
System.out.println("Arrays.stream(int[]).boxed().toList() = " + ok1 + " contains(3)? " + ok1.contains(3));
System.out.println("可変版 = " + ok2);
}
}
実行結果(OpenJDK 21.0.10):
Arrays.asList(int[]).size() = 1
中身 = int[] [3, 1, 2]
contains(3)? false
List.of(int[]).size() = 1
Arrays.asList(Integer[]) = [3, 1, 2] size=3
Arrays.stream(int[]).boxed().toList() = [3, 1, 2] contains(3)? true
可変版 = [3, 1, 2]
size() が 1、contains(3) が false になりました。var や List<int[]> で受けるとコンパイルが通るため気づきにくい点です。List.of(int[]) も同じく要素数1です。
受け取る型を List<Integer> と明示しておくと、コンパイル時に気づけます。 次は List<Integer> list = Arrays.asList(nums); と書いて java Bad.java を実行したときのエラーです(OpenJDK 21.0.10)。
Bad.java:5: error: incompatible types: inference variable T has incompatible bounds
List<Integer> list = Arrays.asList(nums);
^
equality constraints: Integer
lower bounds: int[]
where T is a type-variable:
T extends Object declared in method <T>asList(T...)
1 error
error: compilation failed
| 元の配列 | 直し方 | 結果 |
|---|---|---|
int[] |
Arrays.stream(nums).boxed().toList() |
List<Integer>(不変) |
int[] |
IntStream.of(nums).boxed().collect(Collectors.toCollection(ArrayList::new)) |
List<Integer>(可変) |
Integer[] |
Arrays.asList(boxed) |
要素数3、配列と連動 |
long[]/double[] |
Arrays.stream(arr).boxed().toList() |
同様 |
Set.of・Map.of の重複エラーと要素の順序
Set.of と Map.of は、重複した要素・キーを渡すと IllegalArgumentException になります。 また、反復順(for 文で取り出す順)は決まっていません。
// SetMap.java
import java.util.*;
public class SetMap {
public static void main(String[] args) {
try { Set.of("a", "b", "a"); } catch (Exception e) { System.out.println("Set.of 重複: " + e); }
try { Map.of("k1", 1, "k1", 2); } catch (Exception e) { System.out.println("Map.of 重複キー: " + e); }
try { Set.of("a", null); } catch (Exception e) { System.out.println("Set.of null: " + e); }
try { Map.of("k", null); } catch (Exception e) { System.out.println("Map.of null値: " + e); }
System.out.println("Set.copyOf 重複は許容: " + Set.copyOf(List.of("a", "b", "a")).size());
System.out.println("Map.ofEntries: " + new TreeMap<>(Map.ofEntries(Map.entry("x", 1), Map.entry("y", 2))));
System.out.println("Set.of 反復順: " + Set.of("apple", "banana", "cherry", "date", "elderberry"));
System.out.println("Map.of 反復順: " + Map.of("apple", 1, "banana", 2, "cherry", 3, "date", 4));
System.out.println("順序を保つ: " + new LinkedHashSet<>(List.of("apple", "banana", "cherry", "date", "elderberry")));
System.out.println("List.of(Set.of) は順序?: " + List.of("c","a","b"));
}
}
実行結果(OpenJDK 21.0.10、1回目):
Set.of 重複: java.lang.IllegalArgumentException: duplicate element: a
Map.of 重複キー: java.lang.IllegalArgumentException: duplicate key: k1
Set.of null: java.lang.NullPointerException
Map.of null値: java.lang.NullPointerException
Set.copyOf 重複は許容: 2
Map.ofEntries: {x=1, y=2}
Set.of 反復順: [cherry, date, banana, elderberry, apple]
Map.of 反復順: {banana=2, apple=1, cherry=3, date=4}
順序を保つ: [apple, banana, cherry, date, elderberry]
List.of(Set.of) は順序?: [c, a, b]
同じプログラムを続けて3回実行し、反復順の行だけを取り出した結果です。
Set.of 反復順: [apple, elderberry, banana, date, cherry]
Map.of 反復順: {cherry=3, apple=1, banana=2, date=4}
Set.of 反復順: [cherry, apple, elderberry, banana, date]
Map.of 反復順: {banana=2, date=4, cherry=3, apple=1}
Set.of 反復順: [apple, cherry, date, banana, elderberry]
Map.of 反復順: {date=4, banana=2, apple=1, cherry=3}
Set.of/Map.of の反復順は、JVM を起動するたびに変わりました。 Javadoc でも反復順は「未規定で、変わることがある」とされています。順番に依存したテストや表示は、実行ごとに結果が変わる原因になります。
| 症状 | 原因 | 直し方 |
|---|---|---|
IllegalArgumentException: duplicate element |
Set.of に同じ要素 |
重複がありうるなら Set.copyOf(list) か new HashSet<>(list) |
IllegalArgumentException: duplicate key |
Map.of に同じキー |
キーを見直す。統合したいなら Collectors.toMap(k, v, (a, b) -> b) |
NullPointerException |
null 要素・null キー・null 値 | null を入れない設計にするか HashMap を使う |
| 実行ごとに表示順が変わる | 反復順は未規定 | 挿入順なら LinkedHashSet/LinkedHashMap、並べ替えなら TreeSet/TreeMap |
Map.of に11組以上入れたい |
Map.of は10組まで |
Map.ofEntries(Map.entry(k, v), ...) |
Set.copyOf は重複を許容し、1つにまとめます(実行結果で要素数2)。「リテラルで書くときは重複を早く見つけたい」のが Set.of、「データから作るときは重複をまとめたい」のが Set.copyOf と使い分けます。
Java 21 の SequencedCollection との関係
Java 21 で List に getFirst()/getLast()/reversed()/addFirst() などが加わりました。 不変リストでは、読み取り系は使え、変更系は UnsupportedOperationException になります。Arrays.asList の reversed() は配列とつながったビューです。
// Rev.java
import java.util.*;
public class Rev {
public static void main(String[] args) {
List<String> of = List.of("a", "b", "c");
System.out.println("List.of.reversed() = " + of.reversed() + " getFirst=" + of.getFirst() + " getLast=" + of.getLast());
try { of.reversed().addFirst("z"); } catch (Exception e) { System.out.println("List.of.reversed().addFirst -> " + e); }
String[] arr = {"a", "b", "c"};
List<String> rev = Arrays.asList(arr).reversed();
rev.set(0, "Z");
System.out.println("Arrays.asList(arr).reversed().set(0) -> 配列=" + Arrays.toString(arr));
}
}
実行結果(OpenJDK 21.0.10):
List.of.reversed() = [c, b, a] getFirst=a getLast=c
List.of.reversed().addFirst -> java.lang.UnsupportedOperationException
Arrays.asList(arr).reversed().set(0) -> 配列=[a, b, Z]
逆順ビューの先頭を set すると、元の配列の末尾が Z に変わりました。reversed() もコピーではなくビューです。
Java 25 での違い(公式ドキュメントで確認)
本記事のコードはすべて OpenJDK 21.0.10 で実行しています。Java 25 は手元で実行していません。Java 25 の List の Javadoc を確認したところ、Java 9 以降の追加は of(Java 9)、copyOf(Java 10)、addFirst/addLast/getFirst/getLast/removeFirst/removeLast/reversed(Java 21)で、Java 22〜25 で List に追加されたメソッドはありませんでした。不変リストの説明(null を許可しない、変更操作で UnsupportedOperationException、値ベースである)も同じです。本記事の比較表は Java 25 でもそのまま当てはまります。
なお、不変リストは「値ベース(value-based)」とされており、== での比較や synchronized のロック対象に使わないよう Javadoc に記載されています。本記事の List.copyOf(...) == ... はコピーの有無を観察するためのもので、通常のコードでは equals で比較します。
よくある質問
Q. List.of と Arrays.asList はどちらを使えばよいですか?
A. 変更しない固定のリストなら List.of が基本です。null を拒否し、元の配列ともつながらないので意図が明確です。配列とリストを同時に書き換えたい、または null 要素を含む固定長リストが必要なときに Arrays.asList を選びます。
Q. UnsupportedOperationException が出たらどう直しますか?
A. 変更したいリストを new ArrayList<>(元のリスト) でコピーしてから add/remove します。スタックトレースに ImmutableCollections が出れば List.of 系、AbstractList が出れば Arrays.asList で作られたリストです。
Q. Collections.unmodifiableList と List.copyOf の違いは?
A. unmodifiableList は元リストの読み取り専用ビューで、元を変更すると見える内容も変わります。List.copyOf は独立した不変コピーで、元の変更の影響を受けません。null 要素は前者は許可、後者は NullPointerException です。
Q. Stream.toList() と collect(Collectors.toList()) は同じですか?
A. 違います。Stream.toList()(Java 16)は不変リストを返し、null 要素は許可します。Collectors.toList() は Java 21 の実行では変更できる ArrayList が返りましたが、型と可変性は保証されていません。変更するなら Collectors.toCollection(ArrayList::new) を使います。
まとめ
List.ofは完全な不変リストで、add/remove/set すべて不可、null は作成時もcontains(null)も NPE です。Arrays.asListは配列とつながった固定長リストで、set・sortはでき、add/removeはUnsupportedOperationExceptionです。- 要素を増減するなら
new ArrayList<>(...)でコピーしてから操作します。 Collections.unmodifiableListは読み取り専用のビューで、元リストの変更がそのまま見えます。固定したいならList.copyOfです。Stream.toList()は不変だが null 可、Collectors.toList()は可変性の保証がないので、変更するならtoCollection(ArrayList::new)です。Arrays.asList(int[])は要素数1のList<int[]>になります。Arrays.stream(arr).boxed().toList()で変換します。Set.of/Map.ofは重複でIllegalArgumentException、反復順は実行ごとに変わるので、順序が必要ならLinkedHashSetなどを使います。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java List.ofとArrays.asListの違い|変更可否の比較表」
株式会社RJCでは、Javaのコレクションの正しい使い分けを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。