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

发布于 2024-09-28 19:06:08 字数 409 浏览 5 评论 0原文

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

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

我真的需要使用Collections.unmodifyingMap()来创建此映射吗?使用它有什么好处?除了它们并没有真正变得恒定这一明显事实之外,不使用它还有什么缺点吗?

I have a map of constants, like this:

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

Do I really need to use Collections.unmodifiableMap() to create this map? What is the advantage of using it? Are there any disadvantages of not using it, besides the obvious fact that they are not really becoming constant?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

呆° 2024-10-05 19:06:08

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

class A {
    private Map importantData;

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

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

如果您不将地图引用返回给其他任何人,则不必费心使其不可修改,除非您偏执于使其不可变。你也许可以相信自己不会改变它。

Collections.unmodifiableMap guarantees that the map will not be modified. It's mostly useful if you want to return a read-only view of an internal map from a method call, e.g:

class A {
    private Map importantData;

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

This gives you a fast method that does not risk the client changing your data. It's much faster and more memory efficient than returning a copy of the map. If the client really does want to modify the returned value then they can copy it themselves, but changes to the copy won't be reflected in A's data.

If you are not returning map references to anyone else then don't bother making it unmodifiable unless you are paranoid about making it immutable. You can probably trust yourself to not change it.

微暖i 2024-10-05 19:06:08

Cameron Skinner 上面的声明“Collections.unmodifyingMap 保证地图不会被修改”实际上通常只是部分为真,尽管对于问题中的具体示例来说它恰好是准确的(仅因为字符对象是不可变的)。我将用一个例子来解释。

Collections.unmodabilibleMap 实际上只为您提供保护,确保映射中保存的对象的引用无法更改。它通过限制“放入”到它返回的映射中来实现这一点。但是,原始封装的映射仍然可以从类外部进行修改,因为 Collections.unmodifyingMap 不会复制映射内容。

在保罗提出的问题中,幸运的是,地图中保存的角色对象是不可修改的。然而,一般来说这可能不是真的,Collections.unmodifyingMap 所宣传的不可修改性不应该是唯一的保障措施。例如,请参见下面的示例。

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 ]

startingLocations 映射已被封装,并且只能通过利用 getStartingLocations 方法中的 Collections.unmodifyingMap 返回。然而,该方案通过访问任何对象然后更改它而被破坏,如上面代码中的“尝试 2”所示。可以说,如果映射中保存的对象本身是不可变的,则只能依靠 Collections.unmodifyingMap 来提供真正不可修改的映射。如果不是,我们希望复制映射中的对象,或者在可能的情况下限制对对象修饰符方法的访问。

Cameron Skinner's statement above that "Collections.unmodifiableMap guarantees that the map will not be modified" is actually only partly true in general, although it happens to be accurate for the specific example in the question (only because the Character object is immutable). I'll explain with an example.

Collections.unmodifiableMap actually only gives you protection that the references to objects held in the map cannot be changed. It does that by restricting the 'put' into the map that it returns. However, the original encapsulated map can still be modified from outside the class because Collections.unmodifiableMap does not make any copies of the contents of the map.

In the question posted by Paulo, the Character objects held in the map are luckily unmodifiable. However, in general this may not be true and the unmodifiability advertised by Collections.unmodifiableMap should not be the only safeguard. For instance, see the example below.

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

When you run this example, you see:

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 ]

The startingLocations map is encapsulated and only returned by leveraging Collections.unmodifiableMap in the getStartingLocations method. However, the scheme is subverted by getting access to any object and then changing it, as seen in "Try 2" in the code above. Suffice to say that one can only rely on Collections.unmodifiableMap to give a truly unmodifiable map IF the objects held in the map are themselves immutable. If they're not, we'd want to either copy the objects in the map or otherwise restrict access to the object's modifier methods, if possible.

欢烬 2024-10-05 19:06:08

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

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

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

Wrapping the Map is to ensure the caller won't change the collection. While this is useful in testing, you really should find this sort of bug there, it may not be so useful in production. A simple workaround is to have your own wrapper like.

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

This only wraps the map when assertions are turned on.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文