Java 什么时候(真的)需要不可修改的映射?

Java 什么时候(真的)需要不可修改的映射?,java,collections,unmodifiable,Java,Collections,Unmodifiable,我有一个常数图,如下所示: private static Map<String, Character> _typesMap = new HashMap<String, Character>() { { put ("string", 'S'); put ("normalizedString", 'N'); put ("token", 'T'); /

我有一个常数图,如下所示:

private static Map<String, Character> _typesMap =
        new HashMap<String, Character>() {
        {
            put ("string", 'S');
            put ("normalizedString", 'N');
            put ("token", 'T');
            // (...)
        }
私有静态映射\u类型映射=
新HashMap(){
{
put(“string”,“S”);
put(“normalizedString”,N');
put(“token”,“T”);
// (...)
}

我真的需要使用集合.unmodifiableMap()吗创建此映射?使用它的优点是什么?不使用它有什么缺点吗?除了它们不是真正变为常量这一明显事实之外?

集合。不可修改的映射保证映射不会被修改。如果要从方法c返回内部映射的只读视图,它最有用所有,例如:

class A {
    private Map importantData;

    public Map getImportantData() {
        return Collections.unmodifiableMap(importantData);
    }
}

这为您提供了一种快速方法,不会让客户端更改数据。它比返回映射副本快得多,内存效率也更高。如果客户端确实希望修改返回的值,则他们可以自己复制,但对副本的更改不会反映在a的数据中


如果您不将映射引用返回给任何其他人,则不要麻烦使其不可修改,除非您偏执于使其不可修改。您可以相信自己不会更改它。

包装映射是为了确保调用方不会更改集合。虽然这在测试中很有用,但您确实应该发现此类错误在那里,它在生产中可能没有那么有用。一个简单的解决方法是拥有自己的包装器

public static <K,V> Map<K,V> unmodifiableMap(Map<K,V> map) {
   assert (map = Collections.unmodifiableMap(map)) != null;
   return map;
}
公共静态地图不可修改地图(地图地图){
断言(map=Collections.unmodifiableMap(map))!=null;
返回图;
}

这仅在启用断言时才会包装映射。

Cameron Skinner在上面的陈述“Collections.unmodifiableMap保证映射不会被修改”实际上在一般情况下仅部分正确,尽管对于问题中的特定示例它恰好是准确的(仅因为角色对象是不可变的).我会举例说明


Collections.unmodifiableMap实际上只为您提供保护,使对映射中保存的对象的引用不能更改。它通过将“put”限制到它返回的映射中来实现这一点。但是,原始封装的映射仍然可以从类外部修改,因为Collections.unmodifiableMap不复制地图的内容

在Paulo提出的问题中,幸运的是地图中的角色对象是不可修改的。但是,通常情况下,这可能不是真的,集合宣传的不可修改性。不可修改地图不应该是唯一的保护措施。例如,请参见下面的示例

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

public class SeeminglyUnmodifiable {
   private Map<String, Point> startingLocations = new HashMap<>(3);

   public SeeminglyUnmodifiable(){
      startingLocations.put("LeftRook", new Point(1, 1));
      startingLocations.put("LeftKnight", new Point(1, 2));
      startingLocations.put("LeftCamel", new Point(1, 3));
      //..more locations..
   }

   public Map<String, Point> getStartingLocations(){
      return Collections.unmodifiableMap(startingLocations);
   }

   public static void main(String [] args){
     SeeminglyUnmodifiable  pieceLocations = new SeeminglyUnmodifiable();
     Map<String, Point> locations = pieceLocations.getStartingLocations();

     Point camelLoc = locations.get("LeftCamel");
     System.out.println("The LeftCamel's start is at [ " + camelLoc.getX() +  ", " + camelLoc.getY() + " ]");

     //Try 1. update elicits Exception
     try{
        locations.put("LeftCamel", new Point(0,0));  
     } catch (java.lang.UnsupportedOperationException e){
        System.out.println("Try 1 - Could not update the map!");
     }

     //Try 2. Now let's try changing the contents of the object from the unmodifiable map!
     camelLoc.setLocation(0,0);

     //Now see whether we were able to update the actual map
     Point newCamelLoc = pieceLocations.getStartingLocations().get("LeftCamel");
     System.out.println("Try 2 - Map updated! The LeftCamel's start is now at [ " + newCamelLoc.getX() +  ", " + newCamelLoc.getY() + " ]");       }
}

startingLocations映射是封装的,仅通过利用getStartingLocations方法中的Collections.unmodifiableMap返回。但是,通过访问任何对象然后对其进行更改,会破坏该方案,如“Try 2”中所示在上面的代码中。只需说,如果映射中包含的对象本身是不可变的,则只能依赖Collections.unmodifiableMap来提供真正不可修改的映射。如果不可变,我们希望复制映射中的对象,或者尽可能限制对对象修改器方法的访问。

您不喜欢
final
也可以?final不会使映射不可变,只会使映射的引用不可变。您仍然可以对final引用调用方法(如“put”)。在本例中,
final
是必需的。(除非
\u typesMap
稍后重置为其他映射…)这只是我的问题还是我的问题?“它比返回地图副本更快、内存效率更高。”——这就是我想知道的。:-“它在生产中可能没那么有用。”"--为什么?这是我的观点,它有用吗?真的有必要吗?如果在测试环境中正确测试它,生产中就不需要这样做,因为您已经检查了集合是否从未被修改过。除非您已经测试了测试环境中的每个代码路径,否则无法确定。如果您错过了一个代码路径,通常最好是fail快速(即使在生产中)而不是让集合被修改,并导致一些难以调试的问题。@Kelvin要么你想要快速失败,在这种情况下你想要断言,要么你想要速度,在这种情况下你不想创建没有任何区别的对象。你也不想要不真实的东西修改地图项目,而不是地图本身。地图是不可修改的,但其项目不一定。集合的项目不是集合本身。它们只是聚合的内容。我认为您应该尽快删除此“答案”!
The LeftCamel's start is at [ 1.0, 3.0 ]
Try 1 - Could not update the map!
Try 2 - Map updated! The LeftCamel's start is now at [ 0.0, 0.0 ]