3秒でわかる
扱う型を後から差し込めるようにする仕組み。List<String> のように型を渡すことで、キャストを書かずにコンパイル時点で型の誤りを検出できます。
もう少し詳しく
どういうものか
ジェネリクスは、クラスやメソッドが扱う型を「型パラメータ」として外から与えられるようにする仕組みです。class Box と書けば、Box と Box を同じ実装から作れます。同じロジックを型ごとに複製する必要がなくなり、しかも型が混ざればコンパイルエラーになります。
なぜ必要か
ジェネリクス導入前の Java では、コレクションは何でも Object として受け取っていました。取り出すたびにキャストが必要で、String を入れたつもりの箱に Integer が紛れていても、実行するまで気付けません。落ちるのは本番のリクエスト処理の途中です。型パラメータを付ければ、代入の時点で誤りが分かります。
具体例
List<String> names = new ArrayList<>();
names.add("sato");
// names.add(1); ここでコンパイルエラーになる
String first = names.get(0); // キャスト不要
static <T extends Comparable<T>> T max(List<T> list) {
T best = list.get(0);
for (T v : list) {
if (v.compareTo(best) > 0) best = v;
}
return best;
}extends Comparable のように上限を付けると、比較できる型だけを受け付けられます。
つまずきやすいところ
new T[10] や if (list instanceof List) は書けないList は不可で List になるList に List は代入できない。継承関係は型引数へ引き継がれないため、必要なら List extends Number> のようにワイルドカードを使うList を使うと警告が出て、ジェネリクスの恩恵が消える似た用語との違い
メソッドだけをジェネリックにすることもできます。クラス全体に型パラメータを付けると利用側が常に型を書く必要がありますが、static のようにメソッド単位で付ければ、その呼び出しの間だけ型が決まります。ユーティリティ的な処理を書くときはメソッド単位から始めると過剰にならずに済みます。
C++ のテンプレートは型ごとに実体を生成しますが、Java のジェネリクスはコンパイル時のチェックのみで実行時には消えます。TypeScript の総称型も同様に実行時には残りません。
覚え方
<> は「あとで中身を決める空欄」です。空欄を埋めた瞬間から、その箱は他の型を受け付けなくなります。