Когда нужна немодифицируемая карта (действительно)? - PullRequest
35 голосов
/ 22 октября 2010

У меня есть карта констант, например:

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

Мне действительно нужно использовать Collections.unmodifiableMap() для создания этой карты? В чем преимущество его использования? Есть ли недостатки в том, что они не используются, кроме очевидного факта, что они на самом деле не становятся постоянными?

Ответы [ 3 ]

67 голосов
/ 22 октября 2010

Collections.unmodifiableMap гарантирует, что карта не будет изменена. Это в основном полезно, если вы хотите вернуть доступное только для чтения представление внутренней карты из вызова метода, например:

class A {
    private Map importantData;

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

Это дает вам быстрый метод, который не рискует, когда клиент изменяет ваши данные. Это гораздо быстрее и эффективнее, чем возвращать копию карты. Если клиент действительно хочет изменить возвращаемое значение, он может скопировать его самостоятельно, но изменения в копии не будут отражены в данных А.

Если вы не возвращаете ссылки на карты кому-либо еще, не беспокойтесь о том, чтобы сделать их неизменяемыми, если вы не уверены, что сделаете их неизменными. Вы, вероятно, можете доверять себе, чтобы не изменить его.

30 голосов
/ 04 марта 2014

Приведенное выше утверждение Кэмерона Скиннера о том, что "Collections.unmodifiableMap гарантирует, что карта не будет изменена", на самом деле только частично в целом верно, хотя оно оказывается точным для конкретного примера в вопросе (только потому что объект Character является неизменным). Я объясню на примере.

Collections.unmodifiableMap фактически защищает вас только от того, что ссылки на объекты, хранящиеся на карте, не могут быть изменены. Он делает это, ограничивая «положить» на карту, которую он возвращает. Однако исходная инкапсулированная карта все еще может быть изменена извне класса, потому что Collections.unmodifiableMap не делает никаких копий содержимого карты.

В вопросе, заданном Пауло, объекты персонажей, хранящиеся на карте, к счастью, не могут быть изменены. Тем не менее, в целом это может быть не так, и немодифицируемость, объявленная 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 ]

Карта initialLocations инкапсулируется и возвращается только путем использования Collections.unmodifiableMap в методе getStartingLocations. Тем не менее, схема подрывается, получая доступ к любому объекту, а затем изменяя его, как показано в «Попытка 2» в приведенном выше коде. Достаточно сказать, что можно полагаться только на Collections.unmodifiableMap, чтобы получить действительно неизменяемую карту, если объекты, содержащиеся на карте, сами являются неизменными. Если это не так, мы бы хотели скопировать объекты на карте или иным образом ограничить доступ к методам модификатора объекта, если это возможно.

0 голосов
/ 22 октября 2010

Обертывание карты должно гарантировать, что вызывающая сторона не изменит коллекцию.Хотя это полезно при тестировании, вы действительно должны найти такую ​​ошибку там, но она может быть не так полезна в производстве.Простой обходной путь - иметь свою собственную оболочку, например.

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

Это оборачивает карту, только когда включены утверждения.

...