5

我是 Java 新手,我最近了解到,有时对 Collection 进行深度复制并对其进行不可修改的视图很重要,这样里面的数据才能保持安全和不变。当我尝试练习这个(unmodifiableMap2)时,我收到来自 IDEA 的警告:

unmodifiableMap 可以替换为 'Map.copyOf' 调用

这对我来说是有线的,因为我认为 unmodifiableMap 不仅是底层地图的副本。此外,当我尝试以另一种方式(unmodifiableMap1)创建相同的 unmodifiableMap 时,不会弹出警告!

我应该如何理解 IDEA 的这种行为?

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

public class test {
    public static void main(String[] args) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(1,1);
        map.put(2,2);

        Map<Integer, Integer> map1 = new HashMap<>(map);

        Map<Integer, Integer> unmodifiableMap1 = Collections.unmodifiableMap(map1);

        Map<Integer, Integer> unmodifiableMap2 = Collections.unmodifiableMap(new HashMap<>(map););

    }
}

在此处输入图像描述

4

3 回答 3

3

使用对地图的现有引用的不可修改的地图非常好,您可能想要这样做的原因有很多。

考虑这个类:

class Foo {
    private final Map<String, String> fooMap = new HashMap<>();

    // some methods which mutate the map

    public Map<String, String> getMap() {
        return Collections.unmodifiableMap(fooMap);
    }
}

这个类所做的是提供它封装的地图的只读视图。该类可以确保使用地图的客户无法更改它,他们只能看到它的内容。如果他们保留参考资料一段时间,他们也将能够看到条目的任何更新。

如果我们试图通过复制地图来公开只读视图,则执行复制需要时间和内存,并且客户端不会看到任何更改,因为两个地图都是不同的实例 - 源和副本。

但是在这种情况下:

Collections.unmodifiableMap(new HashMap<>(map));

您首先将映射复制到一个新的哈希映射中,然后将该副本传递到Collections.unmodifiableMap. 结果实际上是恒定的。您没有对使用 创建的副本的引用new HashMap<>(map),也无法获得一个*。

如果您想要的是一个常量映射,那么Map.copyOf这是一种更简洁的实现方式,因此 IntelliJ 建议您应该改用它。

在第一种情况下,由于对地图的引用已经存在,IntelliJ 无法对您的意图做出相同的推断,因此它没有给出这样的建议。

如果您愿意,您可以查看此功能的 IntelliJ 票证,尽管它没有解释为什么两者本质上是等价的,只是它们是等价的。


* 好吧,您可能可以通过反射,但 IntelliJ 假设您不会

于 2021-01-10T21:35:53.287 回答
3

Map.copyOf()制作给定实例的副本Map,但它要求地图中没有任何值是null. 通常情况下是这样,但Map一般来说对 a 的要求并不严格。

java.util.Collections.unmodifiableMap()只是包装对给定Map实例的引用。这意味着接收者无法修改映射,但对原始映射(作为 参数的那个unmodifiableMap())的修改对接收者是可见的。

假设我们有两个线程,一个遍历不可修改的映射,而另一个修改原始映射。结果,你可能会ConcurrentModificationException在不可修改的地图上得到一个操作……调试那个东西并不好笑!

由 .创建的副本不会发生这种情况Map.copyOf()。但这有代价:使用副本,您需要两倍于地图的内存量(大约)。对于非常大的地图,这可能会导致内存不足OutOfMemoryError. 调试也不好玩!

此外,仅包装现有地图可能比复制它要快得多

所以一般情况下没有最好的解决方案,但是对于大多数场景,Map.copyOf()当我需要不可修改的地图时,我会优先使用。


问题中的示例没有包装原始 Map实例,但它在包装之前制作了一个副本(无论是在自己的一行中,还是在运行中)。这消除了“幕后”修改的潜在问题,但可能会带来内存问题。

根据我目前的经验,Map.copyOf( map )看起来比Collections.unmodifiableMap( new HashMap( map ) ).


顺便说一句:Map.copyOf()返回一个类似于HashMap;的地图 当您使用它复制 aTreeMap时,排序顺序会丢失,而包装 withunmodifiableMap()会保留底层Map实现,因此也会保留排序顺序。因此,当这很重要时,您可以使用Collections.unmodifiableMap( new TreeMap( map ) ), whileMap.copyOf()在这里不起作用。

于 2021-01-10T22:05:45.300 回答
0

Map.copyOf(map)完全等价于Collections.unmodifiableMap(new HashMap<>(map))

任何类型的深度复制也没有。但它的执行时间要短得多Maps.copyOf(map)

于 2021-01-11T00:21:23.493 回答