上限境界(extends)を一言でいうと
ジェネリクスの上限境界(extends)は、
「この型パラメータには、あるクラス T か、そのサブクラスだけを許可します」
と制限をかけるための仕組みです。
<T extends Number>List<? extends Number>
のように書きます。
「T は何でもいいよ」ではなく、
「T は Number の一族だけにしたい」「Animal の一族だけにしたい」
といったときに使います。
ここでは、クラスの型パラメータに対する extends と、ワイルドカード ? extends の二つを、例を交えながら丁寧に見ていきます。
クラスの型パラメータに対する上限境界:<T extends 〜>
まずは素のジェネリクスと比較してみる
ジェネリクスなしの「Box」クラスはこうでした。
public class Box {
private Object value;
public void set(Object value) { this.value = value; }
public Object get() { return value; }
}
Javaジェネリクスありで「何でも入る Box」はこう。
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
JavaBox<T> は、Box<String> にも Box<Integer> にも Box<User> にもできます。
「T に何を入れてもいい」という状態です。
ここで、「T は Number か、そのサブクラスにだけしたい」と思ったらどうするか。
<T extends Number> の意味
次のように書きます。
public class NumberBox<T extends Number> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
public double toDouble() {
return value.doubleValue(); // Number のメソッドが使える
}
}
Java<T extends Number> と書いた瞬間に、
「T として使えるのは Number か、そのサブクラスだけ」
「クラスの中では、T を Number として扱っていい」
という二つのことが同時に保証されます。
使う側はこうなります。
NumberBox<Integer> intBox = new NumberBox<>();
intBox.set(10);
System.out.println(intBox.toDouble()); // 10.0
NumberBox<Double> doubleBox = new NumberBox<>();
doubleBox.set(3.14);
System.out.println(doubleBox.toDouble()); // 3.14
// NumberBox<String> x = new NumberBox<>(); // コンパイルエラー
JavaNumberBox<String> はコンパイルエラーになります。String は Number を継承していないからです。
ここが「上限境界」の本質です。
「T に許される“上限(最大の親)”は Number まで。それより上は不可」
という制限を付けているイメージです。
クラスの中で「上限のメソッド」が安全に使えるという強み
<T extends Number> のおかげで、クラス内部では
value は少なくとも Number
したがって doubleValue() などの Number のメソッドが必ず存在する
という前提で書けます。
もし <T> だけだと、value.doubleValue() と書けません。T が何か分からないからです。
extends によって、「T は最低でも Number として振る舞える」という契約を結んでいるわけです。
同じパターンはよく出てきます。
<T extends Comparable<T>> として、compareTo が必ず呼べるようにする。<T extends Animal> として、move() や eat() が必ずある前提で書く。
こういう「上限境界で“能力”を縛る」という設計は、ジェネリクスでとてもよく使います。
メソッドの型パラメータに対する上限境界
<T extends Number> をメソッドに付ける
クラス全体ではなく、「このメソッドだけ Number 系にしたい」というケースもあります。
例えば、「任意の Number 配列の合計を取るメソッド」を考えましょう。
public class NumberUtils {
public static <T extends Number> double sum(T[] array) {
double total = 0;
for (T n : array) {
total += n.doubleValue();
}
return total;
}
public static void main(String[] args) {
Integer[] ints = {1, 2, 3};
Double[] doubles = {1.5, 2.5};
System.out.println(sum(ints)); // 6.0
System.out.println(sum(doubles)); // 4.0
// String[] strings = {"a", "b"};
// sum(strings); // コンパイルエラー
}
}
Javaここでは、メソッド宣言がこうなっています。
public static <T extends Number> double sum(T[] array)
<T extends Number> は、このメソッド専用の型パラメータ宣言です。
「ここで使う T は、Number の一族だけにします」
「だから、このメソッドの中では T を Number として扱ってOKです」
という意味になります。
実際、ループの中で n.doubleValue() を呼べています。
sum(strings) がコンパイルエラーになるのも、「String は Number を継承していないから」です。
クラスの上限境界とメソッドの上限境界の違いは、「スコープ(どこまで有効か)」だけです。
宣言の形はほぼ同じ考え方で理解できます。
ワイルドカード ? extends T の上限境界との違い
<T extends Number> と ? extends Number は何が違うのか
よく混ざるのが、「名前付きの型パラメータ」と「ワイルドカード」です。
<T extends Number>? extends Number
どちらも「Number のサブタイプだけ」という意味ですが、役割が少し違います。
<T extends Number>
何かの宣言側(クラスやメソッド)で「この T は Number 一族」と名前を付ける。
そのメソッド/クラス内で、ずっと同じ T を使い回す。
? extends Number
引数など「利用側」で、“名前はいらないが“Number 一族の何か”を受け取る”という場面で使う。
例えば、次の二つを比べます。
public static <T extends Number> double sum1(List<T> list) { ... }
public static double sum2(List<? extends Number> list) { ... }
Javaどちらも、「Number 系の List から合計を出したい」という用途に使えます。
初心者向けの感覚としては、
「メソッドの中で型に名前を付けて、T として扱い続けたいなら <T extends>」
「ただ Number として読めればよくて、名前はいらないなら ? extends」
くらいで捉えておくと分かりやすいです。
List<? extends Number> に対して「読み取りだけOK、書き込みはほぼ不可」という性質
上限境界ワイルドカード ? extends については、ワイルドカードの説明でも触れましたが、
もう一度「上限境界」の視点から整理します。
List<? 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 系リストを受け取れます。
しかし、list.add(...) のように「具体的な値を追加」することはほぼできません。
なぜか。
List<? extends Number> の実体が、List<Integer> なのか List<Double> なのか List<BigDecimal> なのか分からないからです。
「この 1 行は全ての可能な実体に対して安全か?」をコンパイラが考えたとき、
どんな具体的な Number を add しても、ある型に対しては不正になる可能性があります。
だからコンパイラは「読み取り専用に近い扱い」に制限します。
ここでも、
「extends で上限を決めた時点で、“その上限型として読む”ことは保証されるが、“具体的なサブタイプを書き込む”のは危険」
という性質が見えてきます。
上限境界を使う典型パターン
1. 比較可能なものだけ受け取る:<T extends Comparable<T>>
例えば、「リストの中から最大値を返したい」メソッドを考えます。
要素どうしを比べられないと困るので、「T は Comparable<T> を実装しているものだけ」にしたい。
public static <T extends Comparable<T>> T max(T a, T b) {
return (a.compareTo(b) >= 0) ? a : b;
}
Java<T extends Comparable<T>> という上限境界のおかげで、
T として渡される型は compareTo を必ず持っているa.compareTo(b) が安全に呼べる
という前提で書けます。
使う側はこうです。
System.out.println(max(3, 5)); // OK(Integer は Comparable<Integer>)
System.out.println(max("a", "b")); // OK(String は Comparable<String>)
// max(new Object(), new Object()); // コンパイルエラー
JavaObject は Comparable ではないので、最後の呼び出しはコンパイルできません。
「このメソッドは、“比較できる型”にしか使わせない」という制約を、
上限境界で表現しているわけです。
2. ある抽象クラス/インターフェースの一族にだけ絞る
例えば、「画面に描画できるもの」Drawable インターフェースがあるとします。
public interface Drawable {
void draw();
}
Java「Drawable の一族をまとめて扱うコンテナ」を作るなら、素直に List<Drawable> でもいいのですが、
クラスにジェネリクスで縛りを入れておくこともできます。
public class Canvas<T extends Drawable> {
private final List<T> elements = new ArrayList<>();
public void add(T element) {
elements.add(element);
}
public void drawAll() {
for (T e : elements) {
e.draw();
}
}
}
Javaこれで Canvas<Circle>, Canvas<Rectangle> など、
「Drawable を実装した型だけを受け付ける Canvas」
が作れます。
Canvas<String> のような「描画できないもの」はコンパイルエラーになります。
「このクラスは、Drawable の世界の中でだけ完結してほしい」
そんな意図を、 <T extends Drawable> という上限境界で表現しているわけです。
上限境界を使う時に意識しておきたいこと
「能力」を前提に設計したいときに使う
上限境界の本質は、「この型パラメータは、少なくともこの“能力”を持っている」という前提を作ることです。
<T extends Number>
→ 「数値として扱える(doubleValue などが呼べる)」
<T extends Comparable<T>>
→ 「大小比較できる(compareTo が呼べる)」
<T extends Runnable>
→ 「スレッドとして走らせられる(run が呼べる)」
こうしておくと、クラスやメソッドの中身を書くときに、T に何でも書けるわけではない分、確実に存在するメソッドに頼った設計ができます。
逆に、上限境界を付けない T は、
「何も約束しない。Object としてしか扱えない」
という状態になります。
「上限を強くしすぎると汎用性を失う」バランス感覚
一方で、上限境界を付けすぎると、そのクラス/メソッドは「特定の世界でしか使えない」ものになります。
何でも受けたい汎用ユーティリティなら、あえて <T> のままにしておいた方がいいことも多いです。
<T extends Number> にするか、ただの <T> にするかは、
「この機能は Number 一族に限定した方が自然か?
それとも本当にどんな型でも使ってほしいのか?」
という設計の判断になります。
ここは答えが一つではなく、「どう使ってほしいか」「どう誤用してほしくないか」を考えて決める部分です。
まとめ:上限境界(extends)を自分の中でこう位置づける
ジェネリクスの上限境界(extends)を一言でまとめると、
「型パラメータに“このクラス/インターフェース一族だけ”という上限を付けて、その“能力”を前提にコードを書けるようにする仕組み」
です。
<T extends Number><T extends Comparable<T>>List<? extends Number>
といった形で、
「T は Number の一族」
「T は Comparable を実装している」
「このリストは Number 一族のどれか」
という制約を付けることで、
内部では Number/Comparable として安全に扱える
誤った型に対する利用をコンパイル時に防げる
というメリットが生まれます。
