ジェネリクス: 型安全

<T> で型を抽象化。キャストを排除。

ジェネリクス
型をパラメータ化。
ワイルドカード (?)
任意の型を受け入れる。
PECS
Producer Extends, Consumer Super。
サイズ調整可能な箱 (Adjustable Container)

ジェネリクスは「サイズ調整可能な箱」です。 **Box<T>** = 中身を後で決められる箱 **Box<String>** = 文字列専用の箱 **Box<Integer>** = 整数専用の箱 同じ設計図(クラス)で、中身だけ変えられます。

なぜジェネリクスが必要か?

ジェネリクスは、型をパラメータ化して再利用性を高める仕組みです。例えば、List<String>、List<Integer>、List<User> など、同じロジックを別の型で使いたいとき、ジェネリクスを使うとコードの重複を防げます。また、キャスト((String) list.get(0))のような危険な操作を排除し、コンパイル時に型チェックができるため、バグを未然に防げます。

いつ使うか?

ジェネリクスは、以下のような場面で使います: 1. **型に依存しないロジックを書きたいとき**: 同じロジックを複数の型で再利用したいとき。 2. **コレクションフレームワークを作りたいとき**: List、Map、Setなどのコレクションを型安全に扱いたいとき。 3. **キャストを排除したいとき**: 実行時のClassCastExceptionを防ぎたいとき。 4. **ユーティリティメソッドを作りたいとき**: Collections.sort()やCollections.max()のような汎用的なメソッドを作りたいとき。

実践テクニック

PECSを覚える

PECS(Producer Extends, Consumer Super)は、ジェネリクスの型パラメータを制約するためのルールです。**Producer Extends T** は「Tまたはそのサブタイプ」を意味し、データを書き込むときに使います。**Consumer Super T** は「Tまたはそのスーパータイプ」を意味し、データを読み出すときに使います。これを覚えると、より柔軟なジェネリクスAPIを設計できます。

型消去を活用する

Javaのジェネリクスは「型消去」という仕組みで、実行時には型情報が失われます。これは、ジェネリクスと配列の互換性を保つためです。しかし、instanceof演算子はジェネリクス型では使えないため、Class<T>のようなチェックが必要になることがあります。

ワイルドカードを慎重に使う

ワイルドカード(?)は、任意の型を受け入れる便利な機能ですが、使いすぎると型安全性が失われます。List<?> は読み取り専用、List<? extends Number> は書き込みも可能など、適切なPECSを使って、型パラメータを制限することをお勧めします。

ジェネリクス

Generic Class & Method
// ジェネリッククラス
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String s = stringBox.get(); // キャスト不要
// ジェネリックメソッド
public static <T> T first(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
String first = first(List.of("a", "b", "c")); // "a"
// 複数の型パラメータ
public class Pair<K, V> {
private K key;
private V value;
}
実行結果
Hello\na
Bad
// ❌ Bad: Object でキャスト地獄
List list = new ArrayList();
list.add("text");
String s = (String) list.get(0); // 危険
Good
// ✅ Good: ジェネリクスで型安全
List<String> list = new ArrayList<>();
list.add("text");
String s = list.get(0); // キャスト不要

ワイルドカード

?, extends, super
// 境界付き型パラメータ
public <T extends Number> double sum(List<T> numbers) {
return numbers.stream()
.mapToDouble(Number::doubleValue)
.sum();
}
// ワイルドカード
void printAll(List<?> list) { // 任意の型
for (Object item : list) {
System.out.println(item);
}
}
// 上限境界(読み取り用)
void process(List<? extends Number> nums) {
Number n = nums.get(0); // OK
// nums.add(1); // コンパイルエラー
}
// 下限境界(書き込み用)
void addIntegers(List<? super Integer> list) {
list.add(1); // OK
// Integer i = list.get(0); // コンパイルエラー
}
// PECS: Producer Extends, Consumer Super
Tip: PECS を覚える: 読むなら extends、書くなら super。

合格ライン

ジェネリッククラスを書ける
? extends と ? super の違いを説明できる

参考リンク

演習課題

課題1: ジェネリッククラス
ジェネリックな Pair<K, V> クラスを作成してください。
課題2: PECS
? extends と ? super を使い分けるメソッドを作成してください。

次のステップ