1. copyOf メソッド: List.copyOf、Set.copyOf、Map.copyOf
次のような状況を想像してください。リスト、セット、またはマップを返すメソッドがあり、呼び出し側のコードがそのコレクションの中身を一切変更できないことが重要です。たとえば「サポートする通貨のリスト」や「ユーザーのロール集合」などです。うっかりした追加/削除がビジネスロジックを壊してしまう可能性があります。
Java 10 以前は、手動でコレクションのコピーを返すか、Collections.unmodifiableList(list) のようなラッパーを使うしかありませんでした。ラッパーは自分経由の変更を防ぐだけで、元のコレクションの変更までは防げません。
今はより確実な方法 — copyOf メソッドがあります。
これは何か?
List.copyOf(Collection)、Set.copyOf(Collection)、Map.copyOf(Map)(Java 10)は、渡されたコレクションまたはマップの本物の不変コピーを作成します。
- コピーは元のコレクションと無関係です。オリジナルを変更しても、copyOf の結果には影響しません。
- コピーは変更できません。あらゆる変更操作は UnsupportedOperationException を投げます。
不変コピーの作成例
import java.util.*;
public class CopyOfDemo {
public static void main(String[] args) {
List<String> modifiable = new ArrayList<>();
modifiable.add("Java");
modifiable.add("Python");
// 不変のコピーを作成する
List<String> immutable = List.copyOf(modifiable);
// コピーを変更してみる
try {
immutable.add("C++"); // UnsupportedOperationException を送出する
} catch (UnsupportedOperationException e) {
System.out.println("不変コレクションに要素を追加することはできません!");
}
// 元のリストを変更する
modifiable.add("Kotlin");
// コピーは不変のまま!
System.out.println("元のリスト: " + modifiable);
System.out.println("不変のコピー: " + immutable);
}
}
プログラムの出力:
不変コレクションに要素を追加することはできません!
元のリスト: [Java, Python, Kotlin]
不変のコピー: [Java, Python]
各メソッドの概要
- List.copyOf(Collection) — 元のコレクションの要素を持つ不変リストを返します。
- Set.copyOf(Collection) — コレクションの要素を持つ不変セットを返します。重複は取り除かれます。
- Map.copyOf(Map) — 元のマップのエントリを持つ不変マップを返します。
Set と Map の例
import java.util.*;
public class CopyOfSetMapDemo {
public static void main(String[] args) {
Set<String> modifiableSet = new HashSet<>(Set.of("A", "B", "C"));
Set<String> immutableSet = Set.copyOf(modifiableSet);
// 要素を追加してみる
try {
immutableSet.add("D");
} catch (UnsupportedOperationException e) {
System.out.println("Set は変更できません!");
}
Map<String, Integer> modifiableMap = new HashMap<>();
modifiableMap.put("Alice", 30);
modifiableMap.put("Bob", 25);
Map<String, Integer> immutableMap = Map.copyOf(modifiableMap);
try {
immutableMap.put("Charlie", 28);
} catch (UnsupportedOperationException e) {
System.out.println("Map は変更できません!");
}
}
}
2. copyOf メソッドの特徴と制約
null 要素は許可されない
ファクトリメソッド List.of、Set.of、Map.of と同様に、copyOf も要素・キー・値としての null を禁止します。元のコレクション/マップに null が含まれている場合、コピー中に NullPointerException が投げられます。
List<String> listWithNull = Arrays.asList("A", null, "B");
List<String> immutable = List.copyOf(listWithNull); // NullPointerException を送出する!
コレクションは本当に不変です
add/remove/put/replace の試みは UnsupportedOperationException になります。
元とコピーは独立しています
元のコレクションの変更はコピーに影響せず、その逆も同様です。
渡したコレクションがすでに不変なら — 同じオブジェクトが返されます
すでに不変のコレクション(たとえば List.of(...) の結果)を渡した場合、copyOf は同じオブジェクトを返します(メモリ節約)。
List<String> immutable = List.of("X", "Y");
List<String> copy = List.copyOf(immutable);
System.out.println(immutable == copy); // true
具体的な実装は保証されません
返されるコレクションの型は単に List、Set、Map です。必ずしも ArrayList、HashSet、HashMap とは限りません。実装の詳細(たとえば内部型や最適化)に依存しないでください。契約は不変性と対応するインタフェースであることだけです。
3. copyOf とラッパー (unmodifiable wrappers) の違い
Collections.unmodifiableList(list) は既存のコレクションに対するラッパーを作成します。元のコレクションが変更されると、その変更はラッパーにも見えるようになります。
List.copyOf(list) はオリジナルと無関係な新しい不変コレクションを作成します。
違いのデモ
import java.util.*;
public class WrapperVsCopyOf {
public static void main(String[] args) {
List<String> original = new ArrayList<>(List.of("A", "B"));
// ラッパー
List<String> wrapper = Collections.unmodifiableList(original);
// コピー
List<String> copy = List.copyOf(original);
// 元を変更する
original.add("C");
System.out.println("ラッパー: " + wrapper); // [A, B, C]
System.out.println("コピー: " + copy); // [A, B]
}
}
出力:
ラッパー: [A, B, C]
コピー: [A, B]
ラッパーは元の変更を反映し、コピーは不変のままです。
4. copyOf の実用的な利用シナリオ
メソッドから返す際のデータ保護
public class CurrencyService {
private final List<String> currencies = new ArrayList<>(List.of("USD", "EUR", "JPY"));
public List<String> getSupportedCurrencies() {
// 返された結果を誰も変更できない
return List.copyOf(currencies);
}
}
これで呼び出し側のコードは、返されたリストから通貨を追加・削除できません。
アプリケーション層間の受け渡し
データが層間(DAO → サービス → コントローラ)を渡るときは、途中で誰もコレクションを変更しないことを保証するために copyOf を使いましょう。
マルチスレッドプログラムでの安全な公開
不変コレクションは、追加の同期なしにスレッド間でデータを安全に共有できる便利な方法です。
5. copyOf と不変コレクションのよくある誤り
誤り1: null を挿入しようとする。 copyOf(および of)は null を許しません。リスト/セットの要素としても、マップのキー/値としても不可です。コピー時に NullPointerException になります。
誤り2: ラッパーとコピーの取り違え。 copyOf は独立したコピーを作ります。元の変更はコピーに見えません。一方、Collections.unmodifiableList(list) は単に元のコレクションをラップするだけなので、元の変更はラッパーにそのまま反映されます。
誤り3: 不変コレクションを変更しようとする。 copyOf/of で得たコレクションに対する add/remove/put などの呼び出しは、UnsupportedOperationException を引き起こします。
誤り4: マップのファクトリの制限を忘れる。 たとえば Map.of() はキーと値のペアを最大 10 個までしかサポートしません。より多い場合は、Map.ofEntries(...) を使うか、可変のマップを組み立ててから Map.copyOf を適用します。
誤り5: 具体的な実装に依存する。 copyOf が返すのが ArrayList や HashSet であると期待しないでください。保証されるのはインタフェース(List/Set/Map)と不変性だけです。
GO TO FULL VERSION