static final な List や Map をサクッと宣言しつつ、add() や put() も許さない

Java の final 修飾子は代入を禁止するだけで、インスタンスの内容を変更できなくするわけではない。そのため、リストの内容を追加・変更させないつもりで final と打つだけでは List#add() ができてしまうのだ。

// 要素を変更されたくない List を static final で宣言。インスタンスイニシャライザを使って宣言時に要素を入れておく。
private static final List<String> HOGE_LIST = new ArrayList<String>() {{ add("FUGA"); add("PIYO"); }};

public static void main(String[] args) {
  // final を指定していても追加はできてしまう
  HOGE_LIST.add("Sample");
}

もう登場させてしまったが、今回は Map でもインスタンスイニシャライザが使えるよーというサンプルを見せつつ、要素の追加や変更ができないコレクションを作る方法をまとめる。

ちなみに、以前 List の場合でインスタンスイニシャライザを紹介している。

インスタンスイニシャライザと Collections の併用

早速コードで紹介。

// インスタンスイニシャライザを使ったリスト。final を付けているが後から add() ができてしまう    
private static final List<String> LIST_1 = new ArrayList<String>() {{ add("FUGA"); add("PIYO"); }};

// Collections.unmodifiableList() を使うと、追加や削除ができないリストになる
private static final List<String> LIST_2 = Collections.unmodifiableList( new ArrayList<String>() {{ add("FUGA"); add("PIYO"); }} );

// インスタンスイニシャライザを使ったマップ。サクッと初期値がセットできているが、LIST_1 と同じく変更が効いてしまう
private static final Map<Integer, String> MAP_1 = new HashMap<Integer, String>() {{ put(1, "Hoge"); put(2, "Fuga"); }};

// こちらも Collections.unmodifiableMap() を使うと、追加や削除ができないマップになる
private static final Map<Integer, String> MAP_2 = Collections.unmodifiableMap( new HashMap<Integer, String>() {{ put(1, "Hoge"); put(2, "Fuga"); }} );

Collections.unmodifiable なんたら、なメソッドが、コレクションの型に合わせていくつか存在している。その引数にコレクションを渡すことで、add()put() した時に UnsupportedOperationException が throw される、追加や削除ができないコレクションを生成してくれる (ところでどうして unmodifiable 系のメソッドはオーバーロードで作らなかったんだろう?)。

ここまでやってあげれば、意図した「final な動き」になってくれる。

配列はどうしても中身が変更できてしまう

配列の場合は、array[1] = "New Value"; みたいなことを避ける方法がないようだ。諦めて Collections.unmodifiableList() を使おう。