「Collections.unmodifiableMap はマップが変更されないことを保証する」という上記の Cameron Skinner の声明は、実際には一般的に部分的にしか当てはまりませんが、問題の特定の例ではたまたま正確です (Character オブジェクトが不変であるためのみ)。例を挙げて説明します。
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 を利用することによってのみ返されます。ただし、上記のコードの「Try 2」に示されているように、任意のオブジェクトにアクセスしてから変更すると、このスキームは覆されます。マップに保持されているオブジェクト自体が不変である場合、Collections.unmodifiableMap にのみ依存して真に変更不可能なマップを提供できると言えば十分です。そうでない場合は、マップ内のオブジェクトをコピーするか、可能であればオブジェクトのモディファイア メソッドへのアクセスを制限する必要があります。