あなたの知らない、メソッドに型変数を加えるべき理由〜引数型の実型引数を制限〜
先のエントリは引数の型を制限する話でしたが、今回は引数がパラメータ化された型の場合、その実型引数を制限するのに型変数が使えるという話です。3つのケースを紹介します。
複数の実型引数間の関係を制限
たとえば、A×B (AとBの直積型)を表すクラスPairがあったとして、引数をPair
public static class Pair<A, B> { //... } public static <T> void withPair(final Pair<T, T> pair) { //... }
これにより、withPairはPair
public static void withPairTest() { withPair(new Pair<Integer, Integer>()); // コンパイルエラー // withPair(new Pair<Integer, String>()); }
ネストされた型パラメータの一般化
Javaの総称型は非変なので、ネストされた型パラメータを単純にワイルドカードにすると型の互換性がなくなります。
たとえば、リストのリスト List>を要求するメソッドがあったとして、それを拡張しようとしてメソッドの引数型をList
>にすると、List
>は渡せなくなってしまいます。
public static void withListOfList(final List<List<?>> ls) { //... } public static void withListOfListTest() { //withListOfList(new ArrayList<List<Object>>()); //withListOfList(new ArrayList<List<Integer>>()); withListOfList(new ArrayList<List<?>>()); //withListOfList(new ArrayList<List<? extends Integer>>()); }
そこで、メソッドに型変数Tを導入して、引数型をList>にするとList
>やList
>が渡せるようになります。ただしList
>は渡せなくなります。
public static <T> void withListOfList2(final List<List<T>> ls) { //... } public static void withListOfList2Test() { withListOfList2(new ArrayList<List<Object>>()); withListOfList2(new ArrayList<List<Integer>>()); // ワイルドカードはコンパイルエラー //withListOfList2(new ArrayList<List<?>>()); //withListOfList(new ArrayList<List<? extends Integer>>()); }
ワイルドカードの上限境界を型変数Tにすると、List>とList
>を許すようなものになります。
今度はList>を許しません。
public static <T> void withListOfList3(final List<List<? extends T>> ls) { //... } public static void withListOfList3Test() { // コンパイルエラー // withListOfList3(new ArrayList<List<Object>>()); // withListOfList3(new ArrayList<List<Integer>>()); withListOfList3(new ArrayList<List<?>>()); withListOfList3(new ArrayList<List<? extends Number>>()); }
実は、ワイルドカードを増やせば、withListOfList2とwithListOfList3の両方を含むようなものが作れます。
今度はList
public static void withListOfList4(final List<? extends List<?>> ls) { //... } public static void withListOfList4Test() { withListOfList4(new ArrayList<List<Object>>()); withListOfList4(new ArrayList<List<Integer>>()); withListOfList4(new ArrayList<List<?>>()); withListOfList4(new ArrayList<List<? extends Integer>>()); // 実型引数にサブタイプも許す withListOfList4(new ArrayList<LinkedList<Integer>>()); }
小まとめ
ほどよい一般化を実現するwithListOfList2とwithListOfList3のようなものが欲しい場合は、型変数が必要だといえるでしょう。
実型引数の下限境界を要求
List<?>やList<? extends Number>など、下限境界をもたないワイルドカードを禁止するのに型変数を使うことができます。
ワイルドカードの下限境界にはワイルドカードを書けないので、型変数が必要です。
public static <T> void withList(final List<? super T> list) { //... } public static void withListTest() { withList(new ArrayList<Integer>()); //下限境界あり withList((List<? super Integer>) new ArrayList<Integer>()); //下限境界なし。コンパイルエラー //withList((List<?>) new ArrayList<Integer>()); //withList((List<? extends Number>) new ArrayList<Integer>()); }
なお、すべてのワイルドカードはObjectを上限境界として持つので、このテクニックで上限境界をもたないワイルドカードを禁止することはできません。引数型をList<?>にしたのと同じことになります。
public static <T> void withList2(final List<? extends T> list) { //... } public static <T> void withList2Test() { withList2(new ArrayList<Integer>()); //下限境界あり。上限境界はObject withList2((List<? super Integer>) new ArrayList<Integer>()); //下限境界なし withList2((List<?>) new ArrayList<Integer>()); withList2((List<? extends Number>) new ArrayList<Integer>()); }
大まとめ
- 複数の実型引数間の関係を制限
- ネストされた型パラメータの(ほどよい)一般化
- 型変数の下限境界を要求
に型変数が使える。
2つめ、3つめは使いどころがよくわかりませんが…