ZetCode

Java Collections.unmodifiableMap 方法

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

Collections.unmodifiableMap 方法是 Java 集合框架的一部分。它返回指定 Map 的不可修改视图。此视图阻止通过返回的引用修改底层 Map。

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

基本定义

不可修改的 Map 是现有 Map 的一个包装器,它阻止所有修改操作。尝试修改 Map 将导致 UnsupportedOperationException 异常。此视图是实时的 - 对后备 Map 的更改可以通过不可修改的视图看到。

unmodifiableMap 方法是 java.util.Collections 类中的一个工厂方法。它接受一个 Map 作为参数,并返回该 Map 的不可修改视图。所有 Map 实现都可以通过这种方式进行包装。

创建不可修改的 Map

此示例演示了 Collections.unmodifiableMap 的基本用法。我们创建一个常规的 HashMap,然后获取它的不可修改视图。该示例显示了我们可以从中读取但不能写入不可修改的视图。

UnmodifiableMapBasic.java
package com.zetcode;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class UnmodifiableMapBasic {

    public static void main(String[] args) {
        
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 85);
        scores.put("Bob", 92);
        scores.put("Charlie", 78);
        
        // Create unmodifiable view
        Map<String, Integer> unmodifiableScores = 
            Collections.unmodifiableMap(scores);
        
        // Can read from unmodifiable map
        System.out.println("Bob's score: " + unmodifiableScores.get("Bob"));
        
        try {
            // Attempt to modify unmodifiable map
            unmodifiableScores.put("Diana", 88);
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify unmodifiable map: " + e.getMessage());
        }
        
        // Original map can still be modified
        scores.put("Diana", 88);
        System.out.println("Diana's score (via unmodifiable view): " + 
            unmodifiableScores.get("Diana"));
    }
}

此代码显示了不可修改 Map 的基本行为。我们首先创建一个常规的 HashMap 并用一些条目填充它。然后,我们使用 Collections.unmodifiableMap 创建此 Map 的不可修改视图。

该示例演示了虽然我们无法通过不可修改的视图修改 Map,但对原始 Map 的更改会反映在视图中。这表明不可修改的 Map 是一个实时视图,而不是一个独立的副本。

不可修改 Map 操作

此示例探讨了对不可修改 Map 的各种操作。我们将看到允许哪些操作(读取操作)以及哪些操作抛出异常(写入操作)。该示例涵盖了常见的 Map 接口方法。

UnmodifiableMapOperations.java
package com.zetcode;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class UnmodifiableMapOperations {

    public static void main(String[] args) {
        
        Map<String, String> countries = new HashMap<>();
        countries.put("USA", "Washington");
        countries.put("France", "Paris");
        countries.put("Japan", "Tokyo");
        
        Map<String, String> unmodifiableCountries = 
            Collections.unmodifiableMap(countries);
        
        // Allowed operations
        System.out.println("Size: " + unmodifiableCountries.size());
        System.out.println("Contains France? " + 
            unmodifiableCountries.containsKey("France"));
        System.out.println("Capital of Japan: " + 
            unmodifiableCountries.get("Japan"));
        System.out.println("All countries: " + 
            unmodifiableCountries.keySet());
            
        // Disallowed operations (all throw UnsupportedOperationException)
        try {
            unmodifiableCountries.put("Germany", "Berlin");
        } catch (UnsupportedOperationException e) {
            System.out.println("\nCannot add to unmodifiable map");
        }
        
        try {
            unmodifiableCountries.remove("USA");
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot remove from unmodifiable map");
        }
        
        try {
            unmodifiableCountries.clear();
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot clear unmodifiable map");
        }
    }
}

此示例演示了各种 Map 操作在不可修改 Map 上的行为。像 sizecontainsKeygetkeySet 这样的读取操作可以正常工作。所有修改操作都会抛出 UnsupportedOperationException

输出显示,虽然我们可以查询不可修改的 Map,但任何修改它的尝试都会导致异常。这使得不可修改的 Map 成为提供对 Map 数据只读访问的理想选择。

来自 Java 9 Map.of 的不可修改 Map

Java 9 引入了像 Map.of 这样的工厂方法,它们可以直接创建不可变 Map。此示例将其与 Collections.unmodifiableMap 进行比较。工厂方法创建真正的不可变 Map,而不是视图。

UnmodifiableMapJava9.java
package com.zetcode;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class UnmodifiableMapJava9 {

    public static void main(String[] args) {
        
        // Java 8 and earlier approach
        Map<String, Integer> ages = new HashMap<>();
        ages.put("John", 30);
        ages.put("Mary", 25);
        Map<String, Integer> unmodifiableAges = 
            Collections.unmodifiableMap(ages);
        
        // Java 9+ approach
        Map<String, Integer> immutableAges = 
            Map.of("John", 30, "Mary", 25);
        
        System.out.println("Unmodifiable map: " + unmodifiableAges);
        System.out.println("Immutable map: " + immutableAges);
        
        // Both throw UnsupportedOperationException when modified
        try {
            unmodifiableAges.put("Alice", 28);
        } catch (UnsupportedOperationException e) {
            System.out.println("\nCannot modify unmodifiable map");
        }
        
        try {
            immutableAges.put("Alice", 28);
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify immutable map");
        }
        
        // Difference: original can still modify unmodifiable view
        ages.put("Alice", 28);
        System.out.println("\nAfter original modification: " + unmodifiableAges);
    }
}

此示例突出了 Collections.unmodifiableMap 和 Java 9 的 Map.of 之间的区别。虽然两者都阻止通过它们的引用进行修改,但 unmodifiableMap 只是一个潜在可变 Map 的视图,而 Map.of 创建一个完全不可变的 Map。

输出显示,对原始 Map 的更改会反映在不可修改的视图中,而 Java 9 不可变 Map 无法通过任何方式修改。根据您的不可变性要求选择合适的方法。

嵌套的不可修改 Map

此示例演示了创建包含其他不可修改集合的不可修改 Map。我们将看到如何构建复杂的不可变数据结构。该示例显示了一个从国家到其不可修改的城市列表的 Map。

NestedUnmodifiableMap.java
package com.zetcode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class NestedUnmodifiableMap {

    public static void main(String[] args) {
        
        // Create mutable lists of cities
        List<String> usCities = new ArrayList<>();
        usCities.add("New York");
        usCities.add("Los Angeles");
        usCities.add("Chicago");
        
        List<String> frCities = new ArrayList<>();
        frCities.add("Paris");
        frCities.add("Lyon");
        frCities.add("Marseille");
        
        // Create unmodifiable lists
        List<String> unmodifiableUsCities = 
            Collections.unmodifiableList(usCities);
        List<String> unmodifiableFrCities = 
            Collections.unmodifiableList(frCities);
        
        // Create map of countries to their city lists
        Map countryCities = new HashMap<>();
        countryCities.put("USA", unmodifiableUsCities);
        countryCities.put("France", unmodifiableFrCities);
        
        // Create unmodifiable view of the map
        Map unmodifiableCountryCities = 
            Collections.unmodifiableMap(countryCities);
        
        System.out.println("Country cities: " + unmodifiableCountryCities);
        
        // Attempting to modify at any level will throw exceptions
        try {
            unmodifiableCountryCities.get("USA").add("Houston");
        } catch (UnsupportedOperationException e) {
            System.out.println("\nCannot modify cities list");
        }
        
        try {
            unmodifiableCountryCities.put("Germany", 
                Collections.unmodifiableList(new ArrayList<>(List.of("Berlin"))));
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify countries map");
        }
    }
}

此示例创建一个深度不可变性结构。我们首先使城市列表不可修改,然后将它们放置在一个 Map 中,最后使 Map 本身不可修改。这确保了结构的任何部分都不能被修改。

该示例显示了尝试修改外部 Map 或嵌套集合都会导致异常。当您需要向应用程序的其他部分提供完全不可变的数据结构时,此方法很有用。

性能注意事项

此示例检查不可修改 Map 的性能特征。我们将比较常规 Map 及其不可修改视图上的操作。测试测量读取操作和内存开销。

UnmodifiableMapPerformance.java
package com.zetcode;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class UnmodifiableMapPerformance {

    public static void main(String[] args) {
        
        final int SIZE = 1_000_000;
        final int ITERATIONS = 100;
        
        // Create large map
        Map<Integer, String> bigMap = new HashMap<>();
        for (int i = 0; i < SIZE; i++) {
            bigMap.put(i, "Value" + i);
        }
        
        // Create unmodifiable view
        Map<Integer, String> unmodifiableBigMap = 
            Collections.unmodifiableMap(bigMap);
        
        // Measure get operation performance
        long start = System.currentTimeMillis();
        for (int i = 0; i < ITERATIONS; i++) {
            bigMap.get(i % SIZE);
        }
        long duration = System.currentTimeMillis() - start;
        System.out.println("Regular map get: " + duration + " ms");
        
        start = System.currentTimeMillis();
        for (int i = 0; i < ITERATIONS; i++) {
            unmodifiableBigMap.get(i % SIZE);
        }
        duration = System.currentTimeMillis() - start;
        System.out.println("Unmodifiable map get: " + duration + " ms");
        
        // Measure memory usage
        Runtime runtime = Runtime.getRuntime();
        runtime.gc();
        long memoryBefore = runtime.totalMemory() - runtime.freeMemory();
        
        Map<Integer, String>[] maps = new Map[1000];
        for (int i = 0; i < 1000; i++) {
            maps[i] = Collections.unmodifiableMap(new HashMap<>());
        }
        
        runtime.gc();
        long memoryAfter = runtime.totalMemory() - runtime.freeMemory();
        System.out.println("Memory per unmodifiable map wrapper: " + 
            (memoryAfter - memoryBefore) / 1000 + " bytes");
    }
}

此性能测试表明,不可修改的 Map 对于读取操作具有可忽略的开销。get 操作在原始 Map 及其不可修改视图上花费的时间基本相同。

内存测试表明,每个不可修改的包装器会消耗少量额外的内存(通常为 16-32 字节)。对于大多数应用程序而言,这种开销可以忽略不计,这使得不可修改的视图成为提供只读访问的轻量级解决方案。

实际用例

此示例演示了 unmodifiableMap 在配置管理系统中的实际应用。我们将创建一个配置持有者,该持有者提供对应用程序设置的只读访问,同时允许特权代码更新配置。

ConfigurationManager.java
package com.zetcode;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ConfigurationManager {

    private Map<String, String> configMap = new HashMap<>();
    private Map<String, String> unmodifiableConfigView = 
        Collections.unmodifiableMap(configMap);
    
    public ConfigurationManager() {
        // Initialize with default values
        configMap.put("timeout", "30");
        configMap.put("max_connections", "10");
        configMap.put("debug_mode", "false");
    }
    
    // Public method to get read-only configuration
    public Map<String, String> getConfiguration() {
        return unmodifiableConfigView;
    }
    
    // Privileged method to update configuration
    public void updateConfiguration(String key, String value) {
        configMap.put(key, value);
    }
    
    public static void main(String[] args) {
        ConfigurationManager configManager = new ConfigurationManager();
        
        // Get read-only configuration
        Map<String, String> config = configManager.getConfiguration();
        System.out.println("Initial configuration: " + config);
        
        // Try to modify through unmodifiable view
        try {
            config.put("timeout", "60");
        } catch (UnsupportedOperationException e) {
            System.out.println("\nCannot modify configuration through view");
        }
        
        // Update through privileged method
        configManager.updateConfiguration("timeout", "60");
        System.out.println("Updated configuration: " + config);
    }
}

此示例展示了如何在配置管理系统中使用 Collections.unmodifiableMapConfigurationManager 类维护一个可变的内部 Map,但仅向客户端公开一个不可修改的视图。这确保了外部代码无法直接修改配置。

该示例演示了虽然尝试通过不可修改的视图修改配置会失败,但 updateConfiguration 方法仍然可以修改内部 Map,并且这些更改通过视图可见。这种模式在 API 设计中很常见,用于保护内部状态,同时允许受控修改。

来源

Java Collections.unmodifiableMap 文档

在本教程中,我们深入探讨了 Java Collections.unmodifiableMap 方法。我们涵盖了基本用法、Map 操作、与 Java 9 的 Map.of 的比较、嵌套的不可修改结构、性能注意事项以及实际的配置管理用例。此方法对于提供对 Map 数据的只读访问,同时保持根据需要修改底层 Map 的灵活性非常有用。

作者

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

列出所有Java教程