我有一个常量图,如下所示:
private static Map<String, Character> _typesMap =
new HashMap<String, Character>() {
{
put ("string", 'S');
put ("normalizedString", 'N');
put ("token", 'T');
// (...)
}
我真的需要使用Collections.unmodifiableMap()
来创建此地图吗?使用它有什么好处?有没有使用它的缺点,除了明显的事实,它们并没有真正变得不变?
答案 0 :(得分:67)
Collections.unmodifiableMap保证不会修改地图。如果您想从方法调用返回内部映射的只读视图,这将非常有用,例如:
class A {
private Map importantData;
public Map getImportantData() {
return Collections.unmodifiableMap(importantData);
}
}
这为您提供了一种快速方法,可以避免客户端更改数据的风险。它比返回地图副本更快,内存效率更高。如果客户端确实想要修改返回的值,那么他们可以自己复制,但是对副本的更改不会反映在A的数据中。
如果你没有将地图引用返回给其他任何人,那么除非你是偏执地让它变得不可变,否则不要打扰它是不可修改的。你可以相信自己不要改变它。
答案 1 :(得分:31)
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() + " ]"); }
}
运行此示例时,您会看到:
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 ]
beginLocations映射是封装的,只能通过在getStartingLocations方法中利用Collections.unmodifiableMap来返回。但是,通过访问任何对象然后更改它来破坏该方案,如上面代码中的“尝试2”所示。可以说,如果地图中保存的对象本身是不可变的,那么只能依靠Collections.unmodifiableMap来提供真正不可修改的地图。如果不是,我们要么想要复制地图中的对象,要么限制访问对象的修改方法,如果可能的话。
答案 2 :(得分:-1)
包装地图是为了确保来电者不会更改收藏。虽然这在测试中很有用,但你真的应该在那里找到这种bug,它在生产中可能没那么有用。一个简单的解决方法是拥有自己的包装器,如。
public static <K,V> Map<K,V> unmodifiableMap(Map<K,V> map) {
assert (map = Collections.unmodifiableMap(map)) != null;
return map;
}
这只会在断言打开时包装地图。