ZetCode

Java Collections.unmodifiableList

上次修改时间:2025 年 4 月 20 日

Collections.unmodifiableList 方法是 Java 集合框架的一部分。它提供了一种创建列表的不可变(不可修改)视图的方法。当你需要共享一个列表但防止修改时,这很有用。

不可修改列表是现有列表的包装器,它会在尝试修改时抛出 UnsupportedOperationException 异常。原始列表仍然可以被修改,并且更改将通过不可修改视图可见。

基本定义

不可修改列表是列表的只读视图,不能进行结构性修改。结构性修改包括添加、删除或替换元素。列表的内容仍然可以被正常访问和迭代。

Collections.unmodifiableList 方法将一个 List 作为输入,并返回该列表的不可修改视图。返回的列表实现了所有列表操作,但在尝试修改时会抛出异常。

创建不可修改列表

此示例演示了 Collections.unmodifiableList 的基本用法。我们创建一个可变的 ArrayList,然后获取它的一个不可修改视图。示例显示了成功的读取操作和失败的修改尝试。

BasicUnmodifiableList.java
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 不可修改视图

此示例比较了创建列表的防御性复制与使用不可修改视图。防御性复制创建一个新的独立列表,而不可修改视图只是原始列表的包装器。

CopyVsUnmodifiable.java
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" 添加到原始列表不会影响副本,但会显示在不可修改视图中。根据你是否需要隔离或仅需要只读访问,在这些方法之间进行选择。

嵌套不可修改列表

当使用嵌套列表时,创建不可修改的外部列表不会使内部列表不可修改。此示例演示了此行为,并展示了如何创建深度不可修改的结构。

NestedUnmodifiableLists.java
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 进行比较,并显示它们的差异。

ListOfVsUnmodifiable.java
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.ofCollections.unmodifiableList 之间的关键区别。虽然两者都防止修改,但 List.of 创建了一个真正不可变的列表,没有后备的可变源。

Collections.unmodifiableList 只是一个反映对原始列表更改的视图。当数据固定且预先已知时,通常更喜欢使用 List.of 来创建不可变列表。

性能注意事项

此示例研究了不可修改列表与常规列表的性能特征。我们测量了各种操作所花费的时间,以了解开销。

UnmodifiableListPerformance.java
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);
    }
}

此性能测试表明,不可修改列表对读取操作的开销很小。包装器将所有读取操作委托给底层列表,因此性能与原始可变列表几乎相同。

测量结果表明,迭代、元素访问和大小检查在使用不可修改列表时同样快。唯一的性能影响来自额外的间接方法调用,这在大多数情况下可以忽略不计。

不可修改列表的线程安全

虽然不可修改列表通过视图防止修改,但它们并不能自动使列表线程安全。此示例演示了使用不可修改列表时的线程安全考虑事项。

UnmodifiableListThreadSafety.java
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 设计中的实际应用,其中我们希望公开内部数据而不允许修改。

UnmodifiableListAPI.java
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.unmodifiableListgetData 方法返回一个不可修改的视图,阻止客户端直接修改内部列表。

该示例显示了尝试修改返回的列表会失败,但该类仍然可以通过其自己的方法更新其内部数据。这种模式在 API 设计中很常见,用于保护内部状态,同时允许读取访问。

来源

Java Collections.unmodifiableList 文档

在本教程中,我们深入探讨了 Collections.unmodifiableList。我们涵盖了基本用法、防御性复制与不可修改视图、处理嵌套列表、与 Java 9 的 List.of 的比较、性能考虑因素、线程安全以及实际的 API 设计用例。此方法对于创建列表的只读视图非常有用,尤其是在需要安全共享数据的场景中。

作者

我的名字是 Jan Bodnar,我是一位经验丰富的程序员,在编程领域拥有多年经验。我从 2007 年开始撰写编程文章,至今已撰写了 1,400 多篇文章和八本电子书。凭借超过八年的教学经验,我致力于分享我的知识,并帮助他人掌握编程概念。

列出所有Java教程