Mapの置換にみるジェネリクス表現

Javaジェネリクスについて考察するのに丁度良いテーマを見つけたので忘れないうちに書いておく。

Map を Mapに変換するメソッドを作るという話題。

public static <K,V>
	Map<V,K> swap1(Map<K,V> origin) {
// 略
}

単にMapからMapに変換するなら上記のようなメソッドシグネチャでよい。

ここで、Mapの具象型を帰すようにしたいとする。

public static <K,V,
	R extends Map<V,K>,
	P extends Map<K,V>>
R swap2(P origin, Supplier<R> supplier) {
// 略
}

まず単純に引数のMapと戻り値のMapを型変数PとRにとるようにしてみよう。

JavaジェネリクスC#のようなnew制約をつけれないのでメソッド内部でインスタンス生成する場合はjava.lang.Classを引数に渡してリフレクションでnewInstance()するのが常道ではあるが、ここではJava8のjava.util.function.Supplierを利用してラムダでインスタンス生成をさせよう。

利用する場合の呼び出し方は以下のようになる。

Map<String, Integer> origin = new LinkedHashMap<>();
Map<Integer, String> swap = MapSwapSample.swap2(origin, ()->new TreeMap<>());

具象型をI/Oで揃えれるか?

今回の場合はMapなのでIn側とOut側の実装型はむしろ自由に変えれるほうが便利なのだが、このテーマの元ネタはBiFunction型で引数1と2を入れ替えたいというものだった。

ここで、Javaジェネリクスの表現力ではR extends MapとP extends Mapが同じ具象型であり、かつ、その型引数がであることを制限できない。

かつてジェネリクスの型変数を2段階にバインドする試みをしたことがある。これは技法としては型変数の部分適用であった。これが今回のMapのケースでは利用できないところがミソなのだ。

端的に言えば、ジェネリクスの部分適用はフラットで互いに素な型変数であればカリー化して部分適用をすることが可能になるが、今回のようにKとVとそれをバインドするMapという組み合わせではそれが出来ないのである。

無駄な型変数の除去

さて、PとRが同じ具象型であるというような宣言は出来なかった。いや、そこだけを同じであると宣言することは出来るが、そうするとであることを宣言できなくなるので用をなさないのである。

そうなると残念ながら型変数Pは役に立たないものとなる。

ジェネリクスはスコープを区切って、そこへのInとOutの引数/戻り値などの型の関連性を示すものであることをおさらいしよう。

ジェネリクスの基礎と クラス設計への応用の9ページあたりから参考になるだろう。

どこかひとつだけで用いられている型変数というのはわざわざ型変数でなくてよい。例えばjava.util.Collections#shuffleの引数は型変数を使わず、ワイルドカードでshuffle(List<?> list)と定義されている。

同じように、型変数P extends Mapは宣言する必要がなく、単に引数をMapとすればよい。

このようにして不要な型変数を整理すると

public static <V,K,
	R extends Map<V,K>>
R swap3(Map<K,V> origin, Supplier<R> supplier) {
// 略
}

といった形になった。もっとも利用側は

Map<String, Integer> origin = new LinkedHashMap<>();
Map<Integer, String> swap = MapSwapSample.swap3(origin, ()->new TreeMap<>());

と特に変わらないのであるが、不要な型変数はジェネリクスヘルへの第一歩なので、後のコードメンテナンスを考えるなら削っておいたほうがいい。

ソースコード

今回掲載のソースコードGithubにて公開している。

BiFunction

元ネタは BiFunction から BiFunctionを作るというものだった。

先に結論を述べたとおり、BiFunctionの具象型を投入して同じ具象型のBiFunctionを得るというメソッドをJavaジェネリクスではうまく表現することができない。

せいぜい、

public static <T,U,R>
BiFunction<U,T,R> swap(BiFunction<T,U,R> func) {
	return (u, t)->func.apply(t, u);
}

といったところだろうか。