38

我有一个常量映射,如下所示:

private static Map<String, Character> _typesMap =
        new HashMap<String, Character>() {
        {
            put ("string", 'S');
            put ("normalizedString", 'N');
            put ("token", 'T');
            // (...)
        }

我真的需要用来Collections.unmodifiableMap()创建这张地图吗?使用它有什么好处?不使用它是否有任何缺点,除了它们并没有真正变得恒定的明显事实之外?

4

3 回答 3

72

Collections.unmodifiableMap 保证地图不会被修改。如果您想从方法调用返回内部映射的只读视图,这将非常有用,例如:

class A {
    private Map importantData;

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

这为您提供了一种快速的方法,不会冒客户更改数据的风险。它比返回地图副本更快,内存效率更高。如果客户端确实想要修改返回值,那么他们可以自己复制它,但是对副本的更改不会反映在 A 的数据中。

如果您没有将地图引用返回给其他任何人,那么除非您对使其不可变感到偏执,否则不要费心使其不可修改。您可能可以相信自己不会改变它。

于 2010-10-22T16:56:11.747 回答
33

Cameron Skinner 上面所说的“Collections.unmodifiableMap 保证地图不会被修改”实际上只是部分正确,尽管对于问题中的特定示例来说它恰好是准确的(只是因为 Character 对象是不可变的)。我会用一个例子来解释。

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

在 Paulo 发布的问题中,幸运的是,地图中的 Character 对象是不可修改的。然而,一般来说这可能不是真的,Collections.unmodifiableMap 所宣传的不可修改性不应该是唯一的保障。例如,请参见下面的示例。

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() + " ]");       }
}

运行此示例时,您会看到:

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 ]

起始位置映射被封装,并且仅通过利用 getStartingLocations 方法中的 Collections.unmodifiableMap 返回。但是,通过访问任何对象然后更改它,该方案被颠覆,如上面代码中的“尝试 2”所示。可以说,如果地图中包含的对象本身是不可变的,则只能依靠 Collections.unmodifiableMap 来提供真正不可修改的地图。如果不是,我们希望复制地图中的对象或限制对对象修改器方法的访问,如果可能的话。

于 2014-03-03T20:12:18.380 回答
-2

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

public static <K,V> Map<K,V> unmodifiableMap(Map<K,V> map) {
   assert (map = Collections.unmodifiableMap(map)) != null;
   return map;
}

这仅在打开断言时包装地图。

于 2010-10-22T17:29:40.400 回答