はじめに
<? extends E>や<? super E>といった宣言の意味をまとめる。
非変性
<? extends E>や<? super E>の解説をめる前に、Javaコレクションの「非変性」を確認しておこう。
3つのクラスA,B,Cがあり、以下のような親子関係があるとする。
class A {};
class B extends A {};
class C extends B {};
このとき、Collection<B>型の変数には、B型のコレクションしか代入することができない。AやC型のコレクションは代入できない。このような性質をコレクションの非変性という。Collectionの例としてListを考えてみると、以下のようになる。
List<B> bs;
bs = new ArrayList<B>(); //OK
bs = new ArrayList<A>(); //コンパイルエラー
bs = new ArrayList<C>(); //コンパイルエラー
非変でないと何が問題なのだろうか?
ArrayList<A>はともかくとしても、なんとなくArrayList<C>は代入してもいいような気がしないだろうか?
では、ArrayList<C>が代入できた場合に問題が起こることを、以下のコードで確認してみよう。
List<C> cs = new ArrayList<C>();
List<B> bs = cs; //本来はコンパイルエラーだが、代入できたと仮定する。
bs.add(new B());
C c = cs.get(0); //C型の変数に、CのスーパークラスのB型の値が代入されてしまった!
このように、C型のコレクションがCollection<B>に代入できるとしてしまうと、型安全性が失われてしまうのだ。
非変性は、ジェネリクスが型安全であるために、なくてはならないものなのだ。
非変ではできないこと
しかし、現実には非変でないコレクションを使いたいことがある。
今回はその例として、自前でリストを定義することを考えてみよう。
public class MyList<E> {
public void addAll(Collection<E> collection) {
// 追加処理
}
}
MyList<Number>を考えてみよう。Collection<E>は非変性を持つため、MyList<Number>に対してaddAll()できるコレクションはCollection<Number>しかない。Numberの子クラスにはIntegerやDoubleがあるが、だからといってCollection<Integer>やCollection<Double>をaddAll()することはできない。
MyList<Number> list = new MyList<Number>();
Collection<Number> numbers;
Collection<Integer> integers;
Collection<Double> doubles;
list.addAll(numbers);
list.addAll(integers); //コンパイルエラー
list.addAll(doubles); //コンパイルエラー
このメソッドは使い勝手が悪い。実際には、Collection<Integer>やCollection<Double>も引数にとれるようにしたいと考えるのが普通だろう。つまり、MyList<E>のaddAll()の引数には、E型自身のコレクションとE型のすべてのサブクラスのコレクションをとれるようにしたいのだ。
コレクションが非変だと困る場合は他にもある。例として、MyListの内容を別のコレクションにコピーするメソッドを考えてみよう。
public class MyList<E> {
public void copyInto(Collection<E> collection) {
//リストの内容をcollectionにコピーする処理
}
}
addAll()のときと同じように、MyList<Number>を考えてみると、やはり引数のcollectionは非変なので、copyInto()の引数に取れる型はCollection<Number>しかないことがわかる。
Collection<Number> numbers = new ArrayList<Number>();
Collection<Object> objects = new ArrayList<Object>();
MyList<Number> list = new MyList<Number>();
list.copyInto(numbers);
list.copyInto(objects); //コンパイルエラー
今回はaddAll()とは違い、Collection<Integer>やCollection<Double>を引数に取れるようにしようと考えるのは間違いだ。IntegerやDouble型のコレクションにNumberの要素をコピーするということは、より具象化されたコレクションに抽象的な要素を入れることになってしまうからだ。ピンとこない人は、以下のプログラムがコンパイルエラーになることを思い出そう。
Collection<Integer> integers = new ArrayList<Integer>();
Number number = 1;
integers.add(number); //コンパイルエラー
今回はaddAll()とは逆に、Numberのスーパークラス、つまりObject型のリストにMyList<Number>の型を代入できると使い勝手がよくなる。つまり、MyList<E>のcopyInto()の引数には、E型自身のコレクションとE型のすべてのスーパークラスのコレクションをとれるようにしたいのだ。
extendsとsuper
前項のように、メソッドの引数には非変でないコレクションを取りたいことがある。このようなときにはextendsやsuperを使う。
public class MyList<E> {
public void addAll(Collection<? extends E> collection) {
// 追加処理
}
public void copyInto(Collection<? super E> collection) {
//リストの内容をcollectionにコピーする処理
}
}
<? extends E>は、E型自身とE型のすべてのサブクラスを表す。<? super E>は、E型自身とE型のすべてのスーパークラスを表す。
このような型宣言をすることで、今まで取ることができなかった型を引数に取ることができる。
Collection<Number> numbers = new ArrayList<Number>();
Collection<Integer> integers = new ArrayList<Integer>();
Collection<Object> objects = new ArrayList<Object>();
MyList<Number> list = new MyList<Number>();
list.addAll(numbers); //OK
list.addAll(integers); //OK
list.copyInto(numbers); //OK
list.copyInto(objects); //OK
型安全性と制約
しかし最初に述べたとおり、非変でないということは、コレクションの型安全性が失われてしまうのではないだろうか?
そのとおりだ。そこでJavaでは、型安全性を失わせないために、非変でないコレクションに制約を設けている。
例えば、List<? extends E>はnullしかadd()できない。
List<? extends Number> list;
list.add(null); //OK
Number number = 1;
list.add(number); //コンパイルエラー
また、List<? super E>については、get()はObject型でしか受けられない。
List<? super Number> list;
Object el1 = list.get(0); //OK
Number el2 = list.get(0); //コンパイルエラー
このような制約は、Collectionに限らず、自分で定義した型も含めてすべてのジェネリクス型に存在しているのだが、
今回は詳細な解説に踏み込まないことにする。
参考
Effective Java(第2版) 項目28 APIの柔軟性向上のために境界ワイルドカードを使用する。