あなたの知らない、メソッドに型変数を加えるべき理由〜引数型の実型引数を制限〜

先のエントリは引数の型を制限する話でしたが、今回は引数がパラメータ化された型の場合、その実型引数を制限するのに型変数が使えるという話です。3つのケースを紹介します。

複数の実型引数間の関係を制限

たとえば、A×B (AとBの直積型)を表すクラスPairがあったとして、引数をPair に制限、すなわちA=Bという制約をつけるのに型変数を使います。

    public static class Pair<A, B> {
        //...
    }

    public static <T> void withPair(final Pair<T, T> pair) {
        //...
    }

これにより、withPairはPairは許しますが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つめは使いどころがよくわかりませんが…