什么是不可修改的地图(真的)是必要的?

Pau*_*des 36 java collections unmodifiable

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

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

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

Cam*_*ner 68

Collections.unmodifiableMap保证不会修改地图.如果要从方法调用返回内部映射的只读视图,则最有用,例如:

class A {
    private Map importantData;

    public Map getImportantData() {
        return Collections.unmodifiableMap(importantData);
    }
}
Run Code Online (Sandbox Code Playgroud)

这为您提供了一种快速方法,可以避免客户端更改数据的风险.它比返回地图副本更快,内存效率更高.如果客户真的要修改返回值那么他们可以自己复制,但更改复制将无法在数据中得到反映.

如果你没有返回给任何人地图引用那就不必使其不可修改的,除非你是偏执使得它一成不变的.你可以相信自己不要改变它.

  • "它比返回地图副本更快,内存效率更高." - 这就是我想知道的.:-) (4认同)

Don*_*Don 30

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

Collections.unmodifiableMap实际上只为您提供保护,即无法更改对地图中保存的对象的引用.它通过将'put'限制在它返回的地图中来实现.但是,原始封装的映射仍然可以从类外部进行修改,因为Collections.unmodifiableMap不会生成映射内容的任何副本.

在Paulo发布的问题中,地图中保存的角色对象幸运地无法修改.但是,通常情况可能并非如此,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() + " ]");       }
}
Run Code Online (Sandbox Code Playgroud)

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

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 ]
Run Code Online (Sandbox Code Playgroud)

startsLocations映射是封装的,只能通过在getStartingLocations方法中利用Collections.unmodifiableMap来返回.但是,通过访问任何对象然后更改它来破坏该方案,如上面代码中的"尝试2"所示.可以说,如果地图中保存的对象本身是不可变的,那么只能依靠Collections.unmodifiableMap来提供真正不可修改的地图.如果不是,我们要么想要复制地图中的对象,要么限制访问对象的修改方法,如果可能的话.