ZetCode

Java Collections.unmodifiableSet 方法

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

Collections.unmodifiableSet 方法是 Java 集合框架的一部分。它返回指定集合的不可修改视图。此视图阻止修改操作,同时允许访问集合的元素以进行读取。

当您需要提供对数据的只读访问权限时,不可修改的集合非常有用。它们有助于强制不可变性并防止意外修改。如果您保留对原始集合的引用,则仍然可以修改原始集合。

基本定义

不可修改的集合是现有集合的包装器,它阻止所有修改尝试。它对诸如 addremoveclear 之类的操作抛出 UnsupportedOperationException

Collections.unmodifiableSet 方法是 java.util.Collections 类中的一个静态工厂方法。它接受一个 Set 作为参数,并返回该 Set 的不可修改视图。

创建不可修改的集合

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

BasicUnmodifiableSet.java
package com.zetcode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class BasicUnmodifiableSet {

    public static void main(String[] args) {
        
        Set<String> colors = new HashSet<>();
        colors.add("Red");
        colors.add("Green");
        colors.add("Blue");
        
        Set<String> unmodifiableColors = Collections.unmodifiableSet(colors);
        
        // Read operations work
        System.out.println("Set contains Red: " + unmodifiableColors.contains("Red"));
        System.out.println("Set size: " + unmodifiableColors.size());
        
        try {
            // Modification attempt throws exception
            unmodifiableColors.add("Yellow");
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify unmodifiable set: " + e.getMessage());
        }
    }
}

在此示例中,我们首先创建一个可变的颜色 HashSet。然后,我们使用 Collections.unmodifiableSet 创建一个不可修改的视图。该示例显示了诸如 containssize 之类的读取操作正常工作。

当我们尝试通过添加新颜色来修改集合时,会抛出 UnsupportedOperationException。这演示了返回视图的不可变性质。

不可修改的集合与原始集合

此示例说明了不可修改的集合与其后备集合之间的关系。对原始集合的更改反映在不可修改的视图中,但视图本身无法修改。

UnmodifiableSetRelationship.java
package com.zetcode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class UnmodifiableSetRelationship {

    public static void main(String[] args) {
        
        Set<Integer> numbers = new HashSet<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        
        Set<Integer> unmodifiableNumbers = Collections.unmodifiableSet(numbers);
        
        System.out.println("Original set: " + numbers);
        System.out.println("Unmodifiable view: " + unmodifiableNumbers);
        
        // Modify original set
        numbers.add(4);
        System.out.println("After adding to original:");
        System.out.println("Original set: " + numbers);
        System.out.println("Unmodifiable view: " + unmodifiableNumbers);
        
        try {
            // Attempt to modify unmodifiable view
            unmodifiableNumbers.add(5);
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify unmodifiable view");
        }
    }
}

此示例显示不可修改的集合是原始集合的视图。当我们将一个元素添加到原始集合时,该更改在不可修改的视图中可见。但是,我们无法通过不可修改的视图修改集合。

当使用不可修改的集合时,理解此行为非常重要。不可变性保证仅适用于视图,而不一定适用于基础集合。

创建真正不可变的集合

要创建一个完全不可变的集合,其中既不能修改视图,也不能修改原始集合,我们可以使用 Java 9+ 的 Set.of 或包装原始集合的副本。此示例演示了这两种方法。

TrulyImmutableSet.java
package com.zetcode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class TrulyImmutableSet {

    public static void main(String[] args) {
        
        // Approach 1: Using Set.of (Java 9+)
        Set<String> immutableSet1 = Set.of("Apple", "Banana", "Cherry");
        
        // Approach 2: Wrapping a copy of the original set
        Set<String> original = new HashSet<>();
        original.add("Dog");
        original.add("Cat");
        original.add("Bird");
        
        Set<String> immutableSet2 = Collections.unmodifiableSet(new HashSet<>(original));
        
        System.out.println("Immutable Set 1: " + immutableSet1);
        System.out.println("Immutable Set 2: " + immutableSet2);
        
        try {
            immutableSet1.add("Orange");
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify Set.of created set");
        }
        
        try {
            immutableSet2.add("Fish");
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify wrapped copy set");
        }
        
        // Original can still be modified in approach 2
        original.add("Fish");
        System.out.println("Original modified: " + original);
        System.out.println("Immutable Set 2 remains unchanged: " + immutableSet2);
    }
}

此示例演示了创建真正不可变集合的两种方法。第一种方法使用 Java 9 的 Set.of,它创建一个完全不可变的集合。第二种方法创建原始集合副本的不可修改视图。

关键区别在于,使用第二种方法,原始集合仍然可以被修改,但是这些更改不会影响不可变的视图,因为它基于一个副本。这两种方法都阻止通过返回的集合进行修改。

包含自定义对象的不可修改的集合

此示例显示了 Collections.unmodifiableSet 如何与自定义对象一起使用。不可变性仅适用于集合结构,而不适用于对象本身。如果集合中的对象是可变的,则仍然可以修改它们。

UnmodifiableSetWithObjects.java
package com.zetcode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

class Person {
    private String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return name;
    }
}

public class UnmodifiableSetWithObjects {

    public static void main(String[] args) {
        
        Set<Person> people = new HashSet<>();
        people.add(new Person("Alice"));
        people.add(new Person("Bob"));
        
        Set<Person> unmodifiablePeople = Collections.unmodifiableSet(people);
        
        System.out.println("Original set: " + people);
        System.out.println("Unmodifiable view: " + unmodifiablePeople);
        
        // Can't add or remove from unmodifiable set
        try {
            unmodifiablePeople.add(new Person("Charlie"));
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot add to unmodifiable set");
        }
        
        // Can modify objects in the set
        for (Person p : unmodifiablePeople) {
            if (p.toString().equals("Alice")) {
                p.setName("Alicia");
            }
        }
        
        System.out.println("After modifying objects:");
        System.out.println("Original set: " + people);
        System.out.println("Unmodifiable view: " + unmodifiablePeople);
    }
}

此示例演示了虽然集合结构是不可变的(您无法添加或删除元素),但如果集合中包含的对象是可变的,则仍然可以修改它们。我们创建一个 Person 对象的集合,并使其不可修改。

虽然我们不能向集合中添加新的 Person 对象,但我们可以修改现有的 Person 对象。这表明 Collections.unmodifiableSet 仅提供浅层不可变性。对于深层不可变性,对象本身必须是不可变的。

性能注意事项

不可修改的集合具有最小的性能开销,因为它们只是现有集合的包装器。此示例演示了不可修改集合的性能特征,并与普通集合进行了比较。

UnmodifiableSetPerformance.java
package com.zetcode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class UnmodifiableSetPerformance {

    public static void main(String[] args) {
        
        Set<Integer> largeSet = new HashSet<>();
        for (int i = 0; i < 1000000; i++) {
            largeSet.add(i);
        }
        
        Set<Integer> unmodifiableSet = Collections.unmodifiableSet(largeSet);
        
        // Measure contains operation
        long start = System.nanoTime();
        boolean contains = unmodifiableSet.contains(999999);
        long end = System.nanoTime();
        System.out.println("Unmodifiable set contains: " + (end - start) + " ns");
        
        start = System.nanoTime();
        contains = largeSet.contains(999999);
        end = System.nanoTime();
        System.out.println("Regular set contains: " + (end - start) + " ns");
        
        // Measure iteration
        start = System.nanoTime();
        for (int num : unmodifiableSet) {
            // Just iterate
        }
        end = System.nanoTime();
        System.out.println("Unmodifiable set iteration: " + (end - start) + " ns");
        
        start = System.nanoTime();
        for (int num : largeSet) {
            // Just iterate
        }
        end = System.nanoTime();
        System.out.println("Regular set iteration: " + (end - start) + " ns");
    }
}

此示例比较了普通 HashSet 和其不可修改视图之间的读取操作的性能。我们衡量了包含检查和完整的集合迭代。结果表明,性能开销可以忽略不计。

不可修改的包装器对读取操作的开销很小,因为它只是委托给基础集合。主要成本是额外的函数调用开销,对于大多数用例而言,这可以忽略不计。

在 API 中使用不可修改的集合

当您要返回一个集合但阻止调用者修改它时,不可修改的集合尤其适用于设计 API。此示例演示了此模式。

ApiWithUnmodifiableSet.java
package com.zetcode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

class Configuration {
    private Set<String> allowedDomains = new HashSet<>();
    
    public Configuration() {
        allowedDomains.add("example.com");
        allowedDomains.add("test.com");
        allowedDomains.add("demo.org");
    }
    
    public Set<String> getAllowedDomains() {
        return Collections.unmodifiableSet(allowedDomains);
    }
    
    public void addDomain(String domain) {
        allowedDomains.add(domain);
    }
}

public class ApiWithUnmodifiableSet {

    public static void main(String[] args) {
        
        Configuration config = new Configuration();
        
        Set<String> domains = config.getAllowedDomains();
        System.out.println("Allowed domains: " + domains);
        
        try {
            domains.add("hacker.com");
        } catch (UnsupportedOperationException e) {
            System.out.println("API prevents unauthorized domain addition");
        }
        
        // Proper way to add a domain
        config.addDomain("newdomain.com");
        System.out.println("Updated domains: " + config.getAllowedDomains());
    }
}

此示例显示了一个 Configuration 类,该类维护一组允许的域。它没有直接返回可变集合,而是返回一个不可修改的视图。这可以防止未经授权的修改,同时仍然允许读取访问。

该类提供了一种受控方法 (addDomain) 用于修改集合。这种模式在 API 设计中很常见,您希望封装修改逻辑,同时提供对集合数据的读取访问权限。

包含空值的不可修改的集合

此示例探讨了 Collections.unmodifiableSet 如何处理空值。行为取决于基础集合实现对空值的支持。

UnmodifiableSetWithNulls.java
package com.zetcode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class UnmodifiableSetWithNulls {

    public static void main(String[] args) {
        
        // HashSet allows null
        Set<String> hashSet = new HashSet<>();
        hashSet.add(null);
        hashSet.add("Hello");
        
        Set<String> unmodifiableHashSet = Collections.unmodifiableSet(hashSet);
        System.out.println("HashSet with null: " + unmodifiableHashSet);
        
        // TreeSet doesn't allow null
        Set<String> treeSet = new TreeSet<>();
        try {
            treeSet.add(null);
        } catch (NullPointerException e) {
            System.out.println("TreeSet does not allow null values");
        }
        
        treeSet.add("World");
        Set<String> unmodifiableTreeSet = Collections.unmodifiableSet(treeSet);
        System.out.println("TreeSet without null: " + unmodifiableTreeSet);
    }
}

此示例演示了不可修改的集合相对于空值的行为取决于基础集合实现。HashSet 允许空值,因此它的不可修改视图也包含空值。但是,由于其排序要求,TreeSet 不允许空值,并且尝试添加空值会导致 NullPointerException

在使用不可修改的集合时,请确保基础集合实现支持您需要存储的数据,尤其是在空值方面。

来源

Java Collections.unmodifiableSet 文档

在本教程中,我们深入探讨了 Java Collections.unmodifiableSet 方法。我们介绍了基本用法、与原始集合的关系、创建真正不可变的集合、使用自定义对象、性能考虑因素、API 设计模式以及处理空值。此方法对于提供对集合数据的只读访问权限,同时保持在需要时修改基础集合的灵活性很有价值。

作者

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

列出所有Java教程