Java Collections.unmodifiableList
上次修改时间:2025 年 4 月 20 日
Collections.unmodifiableList 方法是 Java 集合框架的一部分。它提供了一种创建列表的不可变(不可修改)视图的方法。当你需要共享一个列表但防止修改时,这很有用。
不可修改列表是现有列表的包装器,它会在尝试修改时抛出 UnsupportedOperationException 异常。原始列表仍然可以被修改,并且更改将通过不可修改视图可见。
基本定义
不可修改列表是列表的只读视图,不能进行结构性修改。结构性修改包括添加、删除或替换元素。列表的内容仍然可以被正常访问和迭代。
Collections.unmodifiableList 方法将一个 List 作为输入,并返回该列表的不可修改视图。返回的列表实现了所有列表操作,但在尝试修改时会抛出异常。
创建不可修改列表
此示例演示了 Collections.unmodifiableList 的基本用法。我们创建一个可变的 ArrayList,然后获取它的一个不可修改视图。示例显示了成功的读取操作和失败的修改尝试。
package com.zetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BasicUnmodifiableList {
public static void main(String[] args) {
List<String> mutableList = new ArrayList<>();
mutableList.add("Apple");
mutableList.add("Banana");
mutableList.add("Cherry");
// Create unmodifiable view
List<String> unmodifiableList =
Collections.unmodifiableList(mutableList);
// Can read elements
System.out.println("First element: " + unmodifiableList.get(0));
System.out.println("Size: " + unmodifiableList.size());
try {
// Attempt to modify
unmodifiableList.add("Orange");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify: " + e.getMessage());
}
// Original can still be modified
mutableList.add("Orange");
System.out.println("Updated view: " + unmodifiableList);
}
}
此代码演示了如何创建和使用不可修改列表。我们首先创建一个可变列表并用元素填充它。然后,我们使用 Collections.unmodifiableList 创建一个不可修改视图。
该示例演示了虽然我们无法直接修改不可修改视图,但对原始列表的更改会反映在视图中。这使得不可修改列表非常适合提供对内部数据的只读访问。
防御性复制 vs 不可修改视图
此示例比较了创建列表的防御性复制与使用不可修改视图。防御性复制创建一个新的独立列表,而不可修改视图只是原始列表的包装器。
package com.zetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CopyVsUnmodifiable {
public static void main(String[] args) {
List<String> original = new ArrayList<>();
original.add("One");
original.add("Two");
// Defensive copy
List<String> copy = new ArrayList<>(original);
// Unmodifiable view
List<String> unmodifiable = Collections.unmodifiableList(original);
// Modify original
original.add("Three");
System.out.println("Original: " + original);
System.out.println("Copy: " + copy);
System.out.println("Unmodifiable view: " + unmodifiable);
}
}
此示例突出显示了防御性复制和不可修改视图之间的区别。防御性复制完全独立于原始列表,而不可修改视图会反映对原始列表的更改。
输出显示,将 "Three" 添加到原始列表不会影响副本,但会显示在不可修改视图中。根据你是否需要隔离或仅需要只读访问,在这些方法之间进行选择。
嵌套不可修改列表
当使用嵌套列表时,创建不可修改的外部列表不会使内部列表不可修改。此示例演示了此行为,并展示了如何创建深度不可修改的结构。
package com.zetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NestedUnmodifiableLists {
public static void main(String[] args) {
List nested = new ArrayList<>();
nested.add(new ArrayList<>(List.of("A", "B")));
nested.add(new ArrayList<>(List.of("C", "D")));
// Create unmodifiable view of outer list
List unmodifiableOuter =
Collections.unmodifiableList(nested);
// Can't modify outer list
try {
unmodifiableOuter.add(new ArrayList<>());
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify outer list");
}
// Can still modify inner lists
unmodifiableOuter.get(0).add("X");
System.out.println("Modified inner list: " + unmodifiableOuter);
// Create deeply unmodifiable structure
List deeplyUnmodifiable = nested.stream()
.map(Collections::unmodifiableList)
.toList();
try {
deeplyUnmodifiable.get(0).add("Y");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify inner lists either");
}
}
}
此示例显示 Collections.unmodifiableList 仅使外部列表不可修改。除非显式地将其设为不可修改,否则内部列表仍然是可变的。我们演示了这两种情况。
创建深度不可修改结构的解决方案使用 Java 流将 unmodifiableList 应用于每个内部列表,然后将它们收集到不可变的外部列表中。这提供了完全的不可变性。
Java 9 List.of 创建的不可修改列表
Java 9 引入了 List.of 工厂方法,用于创建真正不可变的列表。此示例将它们与 Collections.unmodifiableList 进行比较,并显示它们的差异。
package com.zetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ListOfVsUnmodifiable {
public static void main(String[] args) {
// Java 9+ immutable list
List<String> listOf = List.of("Red", "Green", "Blue");
// Traditional unmodifiable view
List<String> mutable = new ArrayList<>();
mutable.add("Red");
mutable.add("Green");
mutable.add("Blue");
List<String> unmodifiable = Collections.unmodifiableList(mutable);
// Both throw UnsupportedOperationException on modification
try {
listOf.add("Yellow");
} catch (UnsupportedOperationException e) {
System.out.println("listOf is immutable");
}
try {
unmodifiable.add("Yellow");
} catch (UnsupportedOperationException e) {
System.out.println("unmodifiable is unmodifiable");
}
// Difference: listOf is truly immutable
try {
listOf.set(0, "Pink");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify listOf at all");
}
// unmodifiable reflects changes to original
mutable.set(0, "Pink");
System.out.println("unmodifiable shows changes: " + unmodifiable);
}
}
此示例突出了 List.of 和 Collections.unmodifiableList 之间的关键区别。虽然两者都防止修改,但 List.of 创建了一个真正不可变的列表,没有后备的可变源。
Collections.unmodifiableList 只是一个反映对原始列表更改的视图。当数据固定且预先已知时,通常更喜欢使用 List.of 来创建不可变列表。
性能注意事项
此示例研究了不可修改列表与常规列表的性能特征。我们测量了各种操作所花费的时间,以了解开销。
package com.zetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UnmodifiableListPerformance {
public static void main(String[] args) {
final int SIZE = 1_000_000;
List<Integer> mutable = new ArrayList<>(SIZE);
for (int i = 0; i < SIZE; i++) {
mutable.add(i);
}
// Create unmodifiable view
List<Integer> unmodifiable = Collections.unmodifiableList(mutable);
// Measure iteration time
long start = System.nanoTime();
for (int num : unmodifiable) {
// Just iterate
}
long end = System.nanoTime();
System.out.printf("Iteration time: %d ms%n", (end - start) / 1_000_000);
// Measure get operation
start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
unmodifiable.get(i);
}
end = System.nanoTime();
System.out.printf("Get operations: %d ns per get%n", (end - start) / 1000);
// Measure size operation
start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
unmodifiable.size();
}
end = System.nanoTime();
System.out.printf("Size operations: %d ns per size%n", (end - start) / 1000);
}
}
此性能测试表明,不可修改列表对读取操作的开销很小。包装器将所有读取操作委托给底层列表,因此性能与原始可变列表几乎相同。
测量结果表明,迭代、元素访问和大小检查在使用不可修改列表时同样快。唯一的性能影响来自额外的间接方法调用,这在大多数情况下可以忽略不计。
不可修改列表的线程安全
虽然不可修改列表通过视图防止修改,但它们并不能自动使列表线程安全。此示例演示了使用不可修改列表时的线程安全考虑事项。
package com.zetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UnmodifiableListThreadSafety {
public static void main(String[] args) throws InterruptedException {
List<String> sharedList = new ArrayList<>();
sharedList.add("Initial");
// Create unmodifiable view
List<String> unmodifiable = Collections.unmodifiableList(sharedList);
// Thread that reads from unmodifiable view
Thread reader = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Reader sees: " + unmodifiable);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
// Thread that modifies original list
Thread writer = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
sharedList.add("Update " + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
reader.start();
writer.start();
reader.join();
writer.join();
System.out.println("Final state: " + unmodifiable);
}
}
此示例演示了虽然不可修改视图本身对于读取是线程安全的,但底层列表仍可能被其他线程修改。读取器线程通过不可修改视图看到了编写器线程所做的更改。
如果需要真正的线程安全性,请考虑使用 CopyOnWriteArrayList 或同步对原始列表的访问。不可修改的包装器仅防止通过该特定引用进行修改,而不是通过对原始列表的其他引用进行修改。
实际用例:API 设计
此示例展示了 Collections.unmodifiableList 在 API 设计中的实际应用,其中我们希望公开内部数据而不允许修改。
package com.zetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UnmodifiableListAPI {
private final List<String> internalData = new ArrayList<>();
public UnmodifiableListAPI() {
internalData.add("Data1");
internalData.add("Data2");
}
/**
* Returns an unmodifiable view of the internal data.
* Clients can read but not modify the list.
*/
public List<String> getData() {
return Collections.unmodifiableList(internalData);
}
// Internal method to modify the data
public void addData(String item) {
internalData.add(item);
}
public static void main(String[] args) {
UnmodifiableListAPI api = new UnmodifiableListAPI();
// Get unmodifiable view
List<String> data = api.getData();
System.out.println("Initial data: " + data);
// Try to modify (will fail)
try {
data.add("Data3");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify returned list");
}
// Modify through internal method
api.addData("Data3");
System.out.println("Updated data: " + api.getData());
}
}
此示例演示了在通过公共 API 公开内部数据的类中使用 Collections.unmodifiableList。getData 方法返回一个不可修改的视图,阻止客户端直接修改内部列表。
该示例显示了尝试修改返回的列表会失败,但该类仍然可以通过其自己的方法更新其内部数据。这种模式在 API 设计中很常见,用于保护内部状态,同时允许读取访问。
来源
Java Collections.unmodifiableList 文档
在本教程中,我们深入探讨了 Collections.unmodifiableList。我们涵盖了基本用法、防御性复制与不可修改视图、处理嵌套列表、与 Java 9 的 List.of 的比较、性能考虑因素、线程安全以及实际的 API 设计用例。此方法对于创建列表的只读视图非常有用,尤其是在需要安全共享数据的场景中。
作者
列出所有Java教程。