ワイルドカード(?)を一言でいうと
ジェネリクスのワイルドカード ? は、
「ここには“何らかの型”が入るけど、具体的な型は気にしない・決めたくない」というときの “あいまいな型パラメータ”
です。
List<?>List<? extends Number>List<? super Integer>
のように使います。
自分でクラスを書くときは <T> など「名前付きの型パラメータ」を使い、
どんな型が来るか分からないコレクションを「引数として受け取る」ときに ? が活きてきます。
まずは一番素朴な List<?> から
List<?> は「要素の型は何でもいいけど、読み取り専用っぽく扱うリスト」
次の 3 つを比べながらイメージをつかみましょう。
List<String> strings = new ArrayList<>();
List<Object> objects = new ArrayList<>();
List<?> unknown = new ArrayList<String>();
JavaList<String>
これは「要素は必ず String」のリスト。
List<Object>
これは「要素は Object。つまり、何でも入れられる」リスト。String も Integer も User も入ってしまう、かなり「何でもあり」な状態です。
List<?>
これは「要素の“具体的な型”は分からないし決めないが、“どれか 1 種類の型”であることは保証される」リストです。
ここがとても重要です。
List<?> は「何でも混ぜて入っている」わけではなく、
例えば実体が List<String> なら「中身は全部 String」だし、List<Integer> なら「中身は全部 Integer」です。
しかし、? と書いた側からは「それが何かは知らない・決めない」という宣言になります。
List<?> に対してできること・できないこと
典型的な例を見てください。
public static void printAll(List<?> list) {
for (Object e : list) {
System.out.println(e);
}
}
Javaこの printAll メソッドは、
List<String>List<Integer>List<User>
など、あらゆる「要素型 1 種」のリストを受け取れます。
中身を読む(get する)のは OK です。
ただし要素型が何か分からないので、Object として受け取るしかありません。
一方、list.add(...) はどうでしょうか。
public static void addSomething(List<?> list) {
// list.add("hello"); // コンパイルエラー
// list.add(123); // これもコンパイルエラー
list.add(null); // これは唯一 OK
}
JavaList<?> に具体的な値を add することはできません(null だけ例外的に OK)。
理由はこうです。
例えば、実際に渡ってきたリストが List<String> だった場合、list.add(123) は「本来あってはならない状態」を作ってしまいますが、List<?> からは「それが List<String> なのか List<Integer> なのか分からない」。
だから、コンパイラは「どんな非 null も add させない」という安全側の判断をします。
ここで一つ、大事な感覚を持ってください。
List<?> で受け取ったものは「読めるけど書けない(null 以外)」
つまり「ほぼ読み取り専用のリスト」として扱う、ということです。
上限境界ワイルドカード ? extends T(「T のサブタイプ」)
List<? extends Number> の意味
? extends T は、
「T のサブクラス(あるいは T 自身)である“何かの型”のリスト」
という意味です。
例えば、
List<? extends Number> numbers;
Javaこの変数には、
List<Integer>List<Double>List<BigDecimal>
などが代入できます。
「Number の一族が入ったリスト」という宣言だと思ってください。
? extends で受け取ると「読むときだけ Number として扱える」
典型的な使い方は、「数字のリストから合計を出したい」ときです。
public static double sum(List<? extends Number> list) {
double total = 0;
for (Number n : list) {
total += n.doubleValue();
}
return total;
}
Javaこのメソッドは、
List<Integer>List<Double>List<Long>
など、あらゆる「Number のサブタイプのリスト」に対して使えます。
ここでのポイントは、
「中身の要素を取り出すときには、最低でも Number であることが保証されている」
ということです。
だから doubleValue() を安心して呼べます。
しかし ? extends に対しても add はほぼできない
List<? extends Number> に対して add できるか試してみます。
public static void addSomething(List<? extends Number> list) {
// list.add(1); // コンパイルエラー
// list.add(1.0); // これもコンパイルエラー
list.add(null); // これは OK
}
Javaなぜ 1(Integer)も 1.0(Double)もダメなのか。
List<? extends Number> の実体が何か分からないからです。
もしかしたら List<Integer> かもしれないし、List<Double> かもしれないし、List<BigDecimal> かもしれない。
そんなときに、コンパイラは「どの具体型にも“絶対に安全”とは言えないから add を禁止する」判断をします。
ここから見えてくる重要なルールがあります。
? extends T で受け取ったリストは、
「T として“読み出す”ことはできるが、(null 以外)“書き込む”ことはできない」
という性質を持ちます。
List<? extends Number> は、
「Number たちを “読み取る” ための引数」に向いている、ということです。
下限境界ワイルドカード ? super T(「T のスーパータイプ」)
List<? super Integer> の意味
今度は逆向きです。? super T は、
「T か T のスーパークラスである“何かの型”のリスト」
を表します。
例えば、
List<? super Integer> list;
Javaこの変数には、
List<Integer>List<Number>List<Object>
などが代入できます。
「Integer も安全に突っ込める“箱”」というイメージを持ってください。
? super は「T を安全に add できる」ための型
次のメソッドを見てください。
public static void addSomeIntegers(List<? super Integer> list) {
list.add(1); // OK
list.add(2); // OK
list.add(3); // OK
}
Javaこのメソッドは、
List<Integer>List<Number>List<Object>
どれに対しても安全です。
なぜなら、
List<Integer> に Integer を入れるのは当然 OKList<Number> に Integer を入れるのも、Integer は Number のサブクラスだから OKList<Object> に Integer を入れるのも、Integer は Object のサブクラスだから OK
だからです。
つまり ? super Integer は、「Integer を突っ込んでも絶対に問題が起きない箱」という宣言になっています。
ただし get するときはほぼ Object としてしか扱えない
List<? super Integer> から要素を取り出すとどうなるか。
public static void useList(List<? super Integer> list) {
Object o = list.get(0); // これは OK
// Integer i = list.get(0); // コンパイルエラー
}
Javaなぜ Integer i で受け取れないのか。
? super Integer の実体が、例えば List<Object> かもしれないからです。
List<Object> から取り出したものを「必ず Integer だ」とは言えません。String や Double かもしれない。
だから、コンパイラは
「get したものを Integer として扱うのは保証できない」と判断してエラーにします。
結局、? super T で get できる安全な型は Object だけになります。
ここから、もう一つの重要なルールが見えてきます。
? super T で受け取ったリストは、
「T(やそのサブクラス)を add することには向いているが、get したものは Object としてしか扱えない」
という性質になります。
extends と super の違いを一言で整理する(PECS)
ここまでの内容をギュッと格言っぽく言うと、
有名な「PECS」という覚え方になります。
Producer Extends, Consumer Super(PECS)です。
Producer(生産者)は extends
Consumer(消費者)は super
という意味です。
もっと噛み砕くとこうなります。
? extends T
「T を“生産する側(取り出す側)”として使う」
→ 読み取り用。T として get できるが、add は null 以外基本禁止。
? super T
「T を“消費する側(突っ込まれる側)”として使う」
→ 書き込み用。T を add できるが、get は Object としてしか扱えない。
例えば、
「いろんな Number サブクラスのリストから値を読む」なら List<? extends Number>
「Integer を詰め込みたい箱として使いたい」なら List<? super Integer>
という選び方になります。
具体例で「ワイルドカードのありがたみ」を感じる
例1:どんな要素型のリストでも中身を表示したい
ワイルドカードなしだと、こう書かざるを得ません。
public static void printStrings(List<String> list) { ... }
public static void printIntegers(List<Integer> list) { ... }
public static void printUsers(List<User> list) { ... }
Java明らかに重複が多いですよね。
ワイルドカードを使うと、これを 1 つにまとめられます。
public static void printAll(List<?> list) {
for (Object e : list) {
System.out.println(e);
}
}
Java呼ぶ側は、
List<String> strings = List.of("a", "b");
List<Integer> ints = List.of(1, 2, 3);
printAll(strings);
printAll(ints);
Javaのように、どんな型のリストでも渡せます。
「引数として、いろいろな型付きリストを受け取りたいが、中身は読むだけ」という場面で、List<?> はとても役に立ちます。
例2:Number サブタイプのリストの合計を出す
List<Integer>, List<Double> などから合計を取りたい関数。
ワイルドカードなしだと、型ごとにオーバーロードすることになります。
sumInt(List<Integer>)sumDouble(List<Double>)
など。
? extends Number を使えば、これを 1 本にできます。
public static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
Javaどんな Number サブタイプのリストでも渡せますが、
内部では「Number として読むだけ」に徹しています。
例3:Integer を突っ込みたい、より汎用的な箱に add する
例えば、「Integer をどこかのコレクションに足し込みたい」のだけど、
相手が List<Integer> なのか List<Number> なのか List<Object> なのか分からない、という状況。
? super Integer を使えば一つのメソッドで対応できます。
public static void addInts(List<? super Integer> list) {
list.add(1);
list.add(2);
}
Javaこれなら、
List<Integer>List<Number>List<Object>
全部に対して安全に呼べます。
ワイルドカードと型パラメータの使い分けのざっくり指針
初心者が迷いやすいポイントがこれです。
「List<?> で受けるべきか、<T> を使ったジェネリックメソッドにするべきか?」
おおまかな目安はこうです。
メソッドの中で「同じ型パラメータどうしの関係」を表したいとき
(例:引数 1 と引数 2 は同じ型、戻り値も同じ型)
→ <T> を使った方がよい(ジェネリックメソッド)
引数として「どんな型のコレクションでもいいが、中身の具体的な型は意識しない」
→ ワイルドカード(? / ? extends / ? super)を使う
例えば、
「リストをそのまま返すだけ」のときは、明らかに <T> が合います。
public static <T> List<T> identity(List<T> list) {
return list;
}
Java一方、「単に表示するだけ」のときは List<?> の方が自然です。
public static void printAll(List<?> list) { ... }
Javaワイルドカードは「関係性を持たない、“その場限りのあいまいさ” を表現するもの」
型パラメータ <T> は「メソッド全体・クラス全体に跨る、“同じ T であること” の関係を表現するもの」
と捉えると、使い分けの感覚がつかみやすくなります。
まとめ:ワイルドカード(?)を自分の中でこう位置づける
ワイルドカード ? を初心者向けに整理すると、こうなります。
?
「型は何か分からない・決めないけど、“1 種類の型” であることだけは保証される」
? extends T
「T か T のサブクラスの“どれか”のリスト。T として読み取るには向いているが、書き込みはほぼ不可」
? super T
「T か T のスーパークラスのリスト。T を add するには向いているが、get したものは Object として扱う」
そして、PECS(Producer Extends, Consumer Super)という格言。
値を“生産する(取り出す)”側として使うなら ? extends
値を“消費する(突っ込む)”側として使うなら ? super
