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) { Listnested = 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教程。