0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Item 31: Use bounded wildcards to increase API flexibility

Posted at

31.APIの柔軟性のために境界ワイルドカードを使用すべし

Item28でもあったように、型パラメータはinvariantである。よって、例えば、List<String>List<Object>のサブタイプではない。List<Object>のできることすべてが、List<String>にできるわけではないので、リスコフの置換原則にも符合する。(Item10)
時には、もっと柔軟性が必要となることがある。Item29で使用したStackクラスを例に考えてみる。StackクラスのAPIは以下のよう。

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

ここに要素のシーケンスを引数にとり、それらをすべてStackにのせるメソッドを追加することを考える。最初に考えるのは以下のようなものだろう。

// pushAll method without wildcard type - deficient!
public void pushAll(Iterable<E> src) {
    for (E e : src)
        push(e);
}

このメソッドはコンパイルできるが、満足いくものではない。Iterable<E> srcの要素がStackの要素と一致すればうまく動くが、例えば、以下のようなコードはエラーとなる。エラーの理由は、型パラメータはinvariantだからである。

Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

ここで出てくるエラーに対処するためには、型引数にワイルドカードを用いる。

// Wildcard type for a parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}

この対応によって、コンパイルできるようになり、タイプセーフにもなる。

次に、collectionを引数にとり、Stackにたまっている要素をすべてそれに移す、popAllメソッドを考えてみる。素案は以下のよう。

// popAll method without wildcard type - deficient!
public void popAll(Collection<E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

これは要素EがStackのそれと完全に一致した場合にはうまく動くが、そうでない場合は動かない。つまり、以下のコードはコンパイルエラーとなる。

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);

対処するためには、以下のようにここでも型引数にワイルドカードを用いる。

// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

ここでの教訓は明確で、インプットされる引数が、producerまたは、consumerのどちらかの役割を果たすときワイルドカードを使う、ということである。
インプットされる引数がproducer、consumerのどちらの役割も果たす場合には、ワイルドカードを使う必要はない。
使いどころは以下のように記憶する。

PECS stands for producer-extends, consumer-super.

これはつまり、型変数Tがproducerである場合には、<? extends T>を用い、consumerである場合には<? super T>を用いるということを表している。上のStackの例もそのようになっている。

このPECSの具体例を本章の例に適用していく。
Item28の以下のコンストラクタについて考える。

public Chooser(Collection<T> choices)

この引数はproducerの役割を果たすので、PECSに従い、以下のようになる。

// Wildcard type for parameter that serves as an T producer
public Chooser(Collection<? extends T> choices)

次に、Item30の以下のunionメソッドについて考える。

public static <E> Set<E> union(Set<E> s1, Set<E> s2)

引数はどちらもproducerの役割を担うため、以下のようになる。

public static <E> Set<E> union(Set<? extends E> s1,
                               Set<? extends E> s2)

ここでの注意点は、戻り値の型には境界ワイルドカードを使ってはならないということだ。
そのような対応は、ユーザにとって柔軟性が増すというより、クライアントのコードの中でワイルドカードを使うことを強いることになる。
クラスの使用に当たって、ユーザが境界ワイルドカードのことを考えないといけないのは、そのAPIに問題があると考えられる。

Java8より前だと以下のように型を明示してやらねばならない。

// Explicit type parameter - required prior to Java 8
Set<Number> numbers = Union.<Number>union(integers, doubles);

次に、Item30のmaxメソッドについて考える。

public static <T extends Comparable<T>> T max(List<T> list)

これをPECSに当てはめると、以下のようになる。

public static <T extends Comparable<? super T>> T max(
        List<? extends T> list)

ここでは2回PECSを当てはめている。
Comparableは常にconsumerなので、Comparable<T>よりComparable<? super T>を選択するべき
Comparatorも同様なので、Comparator<T>よりComparator<? super T>を選択するべき

そのほかに、ワイルドカード関連で議論すべきは、型パラメータを使うべきか、ワイルドカードを使うべきかの問題がある。
例えば、listのスワップを行うメソッドは、型パラメータを使用した場合と、ワイルドカードを使用した場合の2通りが考えられる。

// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);

どちらが好まれるかというと、2つ目のワイルドカードを使用したメソッドの方がシンプルであるので好まれる。
一般に型変数がメソッド内の一か所にしか現れない場合には、ワイルドカードに置き換えるべきである。
2つ目のメソッドは以下のように、それだけを書くとコンパイルエラーとなる。

public static void swap(List<?> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
}
型 List<capture#2-of ?> のメソッド set(int, capture#2-of ?) は引数 (int, capture#3-of ?) に適用できません

これを解消するためには、ワイルドカードを型にはめるヘルパークラスを作成する。

package tryAny.effectiveJava;

import java.util.List;

public class GenericesTest9 {
    public static void swap(List<?> list, int i, int j) {
        swapHelper(list, i, j);
    }

    // Private helper method for wildcard capture
    private static <E> void swapHelper(List<E> list, int i, int j) {
        list.set(i, list.set(j, list.get(i)));
    }

}

こうすることによって、実装が多少複雑になるが、ユーザにはシンプルなswapメソッドを見せることができる。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?