Javaのジェネリクス入門 — <T>って何?を実務目線で解説
はじめに
Javaのコードを読んでいて、こんな記号に出会ったことはありませんか?
List<String> names = new ArrayList<>();
Map<String, Integer> scores = new HashMap<>();
この<String>や<String, Integer>の部分がジェネリクスです。
さらに進むと、こんなコードも出てきます。
public <T> T getFirst(List<T> list) {
return list.get(0);
}
「<T>って何...?」と戸惑う方、多いと思います。
この記事では、ジェネリクスの正体と、実務での使いどころを初心者向けに解説します。
ジェネリクスとは何か
ジェネリクスは、「型」を後から指定できる仕組みです。
ジェネリクスがない世界
List list = new ArrayList(); // Object型のリスト
list.add("hello");
list.add(123); // 文字列でも数値でも入る
String s = (String) list.get(0); // キャストが必要
String n = (String) list.get(1); // 実行時エラー!
何でも入るけど、取り出す時にキャストが必要。そして型を間違えると実行時エラー。
ジェネリクスがある世界
List<String> list = new ArrayList<>(); // String型のリスト
list.add("hello");
// list.add(123); // コンパイルエラー!
String s = list.get(0); // キャスト不要
型を宣言時に指定するので、間違った型が入らない。取り出す時もキャスト不要。
これが型安全です。
基本的な使い方
1. コレクションで使う
ジェネリクスに最もよく出会うのは、コレクション型です。
List<String> names = new ArrayList<>();
Map<String, Integer> ages = new HashMap<>();
Set<Long> ids = new HashSet<>();
「何の型を入れるリスト/マップ/セットなのか」を明示できます。
2. ダイヤモンド演算子
// Java 6以前(冗長)
List<String> list = new ArrayList<String>();
// Java 7以降(推奨)
List<String> list = new ArrayList<>();
右辺の<>はダイヤモンド演算子と呼ばれ、型を省略できます。
自分でジェネリクスを定義する
ジェネリッククラスの例
「どんな型でも格納できる箱」を作ってみましょう。
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
<T>は**「型パラメータ」と呼ばれる仮の型**です。使う側で実際の型を決めます。
Box<String> strBox = new Box<>();
strBox.set("hello");
String s = strBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(42);
Integer n = intBox.get();
1つのクラスで複数の型に対応できます。
ジェネリックメソッドの例
メソッド単位でもジェネリクスが定義できます。
public <T> T getFirst(List<T> list) {
return list.get(0);
}
// 使う側
List<String> strs = Arrays.asList("a", "b", "c");
String first = getFirst(strs); // "a"
List<Integer> nums = Arrays.asList(1, 2, 3);
Integer firstNum = getFirst(nums); // 1
同じメソッドが、渡された型に応じて動くのがジェネリックメソッドです。
よく使われる型パラメータの名前
型パラメータの名前は自由ですが、慣習があります。
| 名前 | 意味 |
|---|---|
T |
Type(一般的な型) |
E |
Element(コレクションの要素) |
K |
Key(マップのキー) |
V |
Value(マップの値) |
R |
Return(戻り値) |
public interface Map<K, V> {
V get(K key);
V put(K key, V value);
}
実務でよく出会うパターン
パターン1: DTOやエンティティクラスの汎用化
public interface Repository<T, ID> {
T findById(ID id);
List<T> findAll();
void save(T entity);
void deleteById(ID id);
}
// 使う側
public class UserRepository implements Repository<User, Long> {
// ...
}
public class OrderRepository implements Repository<Order, String> {
// ...
}
共通のインターフェースを複数のエンティティで使い回せるのが強みです。
パターン2: APIレスポンスの統一フォーマット
public class ApiResponse<T> {
private boolean success;
private T data;
private String errorMessage;
// getter/setter省略
}
// 使う側
ApiResponse<User> userResponse = ...;
ApiResponse<List<Order>> orderResponse = ...;
「成功フラグ+データ」の共通構造を、中身の型を変えて使える。
パターン3: Optional や Stream で型を保つ
Optional<String> name = Optional.of("Alice");
Stream<Integer> numbers = Stream.of(1, 2, 3);
これらも全てジェネリクスで、中に入っている型を保ったまま処理できます。
境界型(extends / super)
もう一歩進んだ話です。「どんな型でもいいわけじゃなく、ある条件を満たす型だけ」にしたい時。
<T extends 〇〇>(上限境界)
public <T extends Number> double sum(List<T> list) {
double total = 0;
for (T num : list) {
total += num.doubleValue();
}
return total;
}
// 使える型: Integer, Double, Long など Number のサブクラス
sum(Arrays.asList(1, 2, 3)); // OK
sum(Arrays.asList(1.5, 2.5)); // OK
// sum(Arrays.asList("a", "b")); // コンパイルエラー
「Number型のサブクラスのみ」と制限することで、Numberのメソッド(doubleValue等)を安全に呼べます。
<? extends 〇〇> と <? super 〇〇>(ワイルドカード)
// 読み取り専用
List<? extends Number> readable = numbers;
// 書き込み専用
List<? super Integer> writable = ...;
読み取りか書き込みかで使い分けます。初学者のうちは深入りしなくてOKです。
ジェネリクスを使う時の注意点
1. プリミティブ型は使えない
// NG
List<int> list = new ArrayList<>();
// OK(ラッパークラス)
List<Integer> list = new ArrayList<>();
intではなくIntegerを使う必要があります。
2. 実行時には型情報が消える(型消去)
List<String> list = new ArrayList<>();
// ↓ 実行時はこう扱われる
List list = new ArrayList();
Javaのジェネリクスはコンパイル時のみ有効で、実行時には消えます。これが時々制限を生みます。
// NG: new T() はできない
public <T> T create() {
return new T(); // コンパイルエラー
}
まとめ
| ポイント | 説明 |
|---|---|
| ジェネリクスは型のパラメータ化 | 使う時に型を指定する |
| 型安全になる | 間違った型が入らない、キャスト不要 |
| 慣習的な名前 | T, E, K, V, R |
| 自作クラスにも使える | Repository, ApiResponseなど |
| 境界型で条件を付けられる | <T extends Number> |
おわりに
ジェネリクスは初見だと「<T>って何...」と戸惑いますが、コレクションで毎日使っているあの機能だとわかると、一気に身近になります。
「型安全」と「再利用性」の両立こそが、ジェネリクス最大のメリットです。
まずはList<String>のように使う側から慣れていき、徐々に自分でジェネリッククラス・メソッドを書けるようになると、Javaの設計力が一段上がります。