Когда нужна немодифицируемая карта (действительно)?
У меня есть карта констант, например:
private static Map<String, Character> _typesMap =
new HashMap<String, Character>() {
{
put ("string", 'S');
put ("normalizedString", 'N');
put ("token", 'T');
// (...)
}
Мне действительно нужно использовать Collections.unmodifiableMap()
создать эту карту? В чем преимущество его использования? Есть ли недостатки в том, что они не используются, кроме очевидного факта, что они не становятся постоянными?
3 ответа
Collections.unmodifiableMap гарантирует, что карта не будет изменена. Это в основном полезно, если вы хотите вернуть доступное только для чтения представление внутренней карты из вызова метода, например:
class A {
private Map importantData;
public Map getImportantData() {
return Collections.unmodifiableMap(importantData);
}
}
Это дает вам быстрый метод, который не рискует, когда клиент изменяет ваши данные. Это намного быстрее и эффективнее, чем возвращение копии карты. Если клиент действительно хочет изменить возвращаемое значение, он может скопировать его самостоятельно, но изменения в копии не будут отражены в данных А.
Если вы не возвращаете ссылки на карту кому-либо еще, не беспокойтесь о том, чтобы сделать ее неизменяемой, если только вы не уверены, что можете сделать ее неизменной. Возможно, вы можете доверять себе, чтобы не изменить его.
Приведенное выше заявление Кэмерона Скиннера о том, что "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. Тем не менее, схема подрывается, получая доступ к любому объекту, а затем изменяя его, как показано в "Try 2" в приведенном выше коде. Достаточно сказать, что можно полагаться только на Collections.unmodifiableMap, чтобы получить действительно неизменяемую карту, если объекты, содержащиеся на карте, сами являются неизменными. Если это не так, мы бы хотели скопировать объекты на карте или иным образом ограничить доступ к методам модификатора объекта, если это возможно.
Обертывание карты должно гарантировать, что вызывающая сторона не изменит коллекцию. Несмотря на то, что это полезно при тестировании, вы действительно должны найти такую ошибку там, но она может быть не так полезна в производстве. Простой обходной путь - иметь свою собственную оболочку.
public static <K,V> Map<K,V> unmodifiableMap(Map<K,V> map) {
assert (map = Collections.unmodifiableMap(map)) != null;
return map;
}
Это только оборачивает карту, когда утверждения включены.