HttpSessionを型安全にする

Servletをやった人はおなじみのjavax.servlet.http.HttpSessionのsetAttribute()/getAttribute()のようなモノをどうやって型安全にするかというのが今回のテーマ。

擬似的にはjava.util.Map型へのput/getだと思ってもらっていい。通常、Mapを使う際はキーの型と値の型を揃えてMapみたいにして使うわけだけども、汎用key-valueストア的な用途になるところはそうもいかなかったりする。

あるいは、O/Rマッピングを用いているような場合に汎用のオブジェクトとしてMapが使われることもある。DBのカラム名をキーに値を格納していたりするわけで、やはりMap型として扱わざるをえなかったりする。

これに対する解はいくつかあって、今回はメソッドスコープの型変数を用いる。

public class KeyValue {
	Map<Key<?>, Object> map = new HashMap<Key<?>, Object>();
	public <T> void put(Key<T> key, T value){
		map.put(key, value);
	}
	@SuppressWarnings("unchecked")
	public <T> T get(Key<T> key){
		return (T)map.get(key);
	}
}

これに対してKeyの定義は

public final class Key<T> {
	private Key() {}

	public static final Key<String> KEY_1 = new Key<String>();
	public static final Key<Integer> KEY_2 = new Key<Integer>();
}

といった感じ。いわゆるTypesafeEnumパターン*1だ。なぜenumじゃないのかというとenumは型変数を持つことができないから、というシンプルな理由による。

このクラスを抽象化できるか?

さて、本題。このKeyValueクラスはキーの値が具象型Keyに固定されている。これをジェネリクスで可変にして抽象化することができるだろうか?

単純にKey型を型変数Kに置き換えてみるとしよう。

public class KeyValue<K> {
  public <T> void put(K<T> key, T value){}
}

コンパイルエラーがでるはずだ。原因個所はKで型変数Kが型変数を取れないことによる。
Key<T>型のような「型変数<T>を持つ型」という境界をKに対して行うことができない。Javaジェネリクスは型変数に対する境界として継承階層の上限と下限しかとることができないのである。

Keyをinterface化してKeyInterfaceを作り、型変数KKを作って&で繋ぐという方法はどうかと試してもみたのだが…

public class KeyValue<K extends KeyInterface<?>> {
	public <T, KK extends K & KeyInterface<T>> void put(KK key, T value){}
}

これは K & の部分で怒られる。型変数に&でinterfaceをつなぐことができないのだ。

これを解決するためにはどのようなプログラミングパラダイムが必要か。

方向のひとつは、型変数が型変数を持てるようにするという方向だ。高階型変数のようなものを整備するという考え方である。

僕はこの辺について、具体的な答えを持っていないのだけど、型システムを考える上で面白いテーマだと思っている。

*1:Effective Javaという本で紹介されたJava5以前で列挙を表現する手法。解説は故桜庭さんの記事などを参照のこと