2010-10-22 47 views
30

我有地圖的常量,像這樣:什麼時候是不可修改的地圖(真的)是必要的?

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

我真的需要使用Collections.unmodifiableMap()創建此地圖?使用它的優點是什麼?除了顯而易見的事實,它們並不真正變得不變,還有沒有使用它的缺點?

+3

你不喜歡'final'嗎? – 2010-10-22 17:01:35

+20

最終將不會使地圖不可變,只會引用地圖。您仍然可以在最終引用上調用方法(如'put')。 – 2010-10-22 17:04:55

+0

在這個例子中,'final'是必要的。 (除非'_typesMap'稍後重置爲不同的地圖...) – 2010-11-28 12:13:13

回答

59

Collections.unmodifiableMap保證地圖不會被修改。如果你想從一個方法調用返回的內部地圖的只讀視圖這主要是有用的,e.g:

class A { 
    private Map importantData; 

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

這給了你不冒險客戶端更改數據的快速方法。與返回地圖副本相比,它速度更快,內存效率更高。如果客戶確實想要修改返回的值,那麼他們可以自己複製它,但對副本的更改不會反映在A的數據中。

如果你沒有返回地圖引用給任何人,那麼不要打擾使它不可修改,除非你是不確定的使它不可變。你可以相信自己不要改變它。

+3

「與返回地圖副本相比,它速度更快,存儲效率更高。」 - 這就是我想知道的。 :-) – 2010-10-22 17:15:09

-2

包裝地圖是爲了確保調用者不會更改集合。雖然這在測試中很有用,但您確實應該在這裏找到這種錯誤,但它在生產中可能不會那麼有用。一個簡單的解決方法是有你自己的包裝。

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

斷言時被接通這僅包裹地圖。

+0

「它在生產中可能不那麼有用」 - 爲什麼?這是我的觀點,有用嗎?真的有必要嗎? – 2010-10-22 17:33:52

+0

如果您在測試環境中正確地進行測試,則不應在生產中使用此功能,因爲您已檢查過該集合從未被修改過。 – 2010-10-23 07:02:56

+1

除非您測試了測試環境中的所有代碼路徑,否則無法確定。如果你錯過了一段代碼路徑,通常會更快地失敗(甚至在生產中),而不是讓集合被修改,並導致一些難以調試的問題。 – Kelvin 2016-07-05 16:05:06

24

卡梅倫Skinner的上述聲明說:「Collections.unmodifiableMap保證地圖將不會被修改」,實際上是隻一般部分如此,儘管它正好是準確的具體示例中的問題,(只因爲性格對象是不可變的)。我會用一個例子來解釋。

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 ] 

的startingLocations映射封裝並僅在getStartingLocations方法利用Collections.unmodifiableMap返回。然而,通過訪問任何對象然後改變它,這個方案被顛覆了,如上面代碼中的「嘗試2」所示。只需說一個人只能依靠Collections.unmodifiableMap來給出一個真正不可修改的地圖,如果地圖中的對象本身是不可變的。如果他們不是,我們想要複製地圖中的對象,或者如果可能的話,限制對對象修飾符方法的訪問。

+0

感謝您的澄清 – charany1 2017-04-10 04:31:35

相關問題