[Java] ジェネリックメソッドの戻り値でワイルドカードを使うと

2017年10月23日月曜日

Java ジェネリクス

Collections.addAllとArrays.asList

ジェネリックメソッドで可変長引数を使うと警告が出るでSetにデータを詰めるのに Collections.addAll を使ったんだけど、この手のもので、変数宣言と同時に使われる記法としては Arrays.asList を使うアイデアがある。
    private static final Set<String> fruits = new HashSet<>(Arrays.asList("1", "2", "3"));

Collections.addAllとArrays.asListのインターフェースは以下のようになっている。

public static <T> boolean addAll(Collection<? super T> c, T... elements)
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Collections.html#addAll-java.util.Collection-T...-

public static <T> List<T> asList(T... a)
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Arrays.html#asList-T...-

Arrays.asListはListに特化しているというのはあるにせよ、どちらも、型変数の可変長引数を受け取って、Collectionに可変長引数の値を詰め込んでいる。

ここで違うのはCollections.addAllの格納先の型パラメータは<? super T>となっていて、Arrays.asListは<T>となっているところ。

どちらも T... a の格納先なので、<? super T>でいいような気がするけど、なんでArrays.asListは<T>になってるんだろうか?

メソッドを使う側は?

Guidelines for Wildcard Use
These guidelines do not apply to a method's return type. Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.

とあるように、戻り値の型にワイルドカードを使ってしまうと、このメソッドを呼ぶ側にもワイルドカードの使用を強いることになってしまう。
    private static final Set<? super String> fruits = readOnlySet("apple", "banana", "orange");

    private static <T> Set<? super String> readOnlySet(T ... a) {
        Set<? super String> set = new HashSet<>(a.length);
        Collections.addAll(set, a);
        return Collections.unmodifiableSet(set);
    }

今回の場合は不変のリストだからまだいいけど、リストを別の変数に代入するとこれはちょっとまずいことになる。

Guidelines for Wildcard Use では <? extends ...> の例が載っているので、 <? super ...> で試してみよう。
        Set<String> sstring = new HashSet<>();
        Set<? super String> sobject = new HashSet<>();

        sobject = sstring;
        sobject.add("foo");
        sobject.add(new Object());   // コンパイルエラー!

この場合、sobjectにStringのスーパークラス型の変数をaddしようとした時点でコンパイルエラーになってしまう。

というわけで、ジェネリックメソッドの戻り値にワイルドカードは使わない方がいいみたい。