「型パラメータの宣言」を一言でいうと
型パラメータの宣言は、
「クラスやメソッドの“名前の横に” <T> などを書いて、ここには後で具体的な型をはめ込みますよ、と宣言すること」
です。
class Box<T> { ... } の <T>public static <T> void doSomething(T value) の <T>
この「 <T> の部分」が、型パラメータの“宣言”そのものです。
クラスの型パラメータ宣言をかみ砕く
class Box<T> の「T」は何者か
まずは一番よく見る形からいきます。
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
Javaclass Box<T> の <T> が「型パラメータの宣言」です。
ここでやっていることを、言葉で分解するとこうなります。
「Box というクラスは、T という『まだ具体的ではない型』を 1 個受け取るジェネリッククラスです」
「このクラスの中では、T を“普通の型名”みたいに使ってよいです」
だから private T value; と書けるし、set(T value) や T get() も書けるわけです。
使う側で、初めて T に具体的な型を入れます。
Box<String> stringBox = new Box<>();
stringBox.set("hello");
String s = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(123);
Integer i = intBox.get();
JavaBox<String> という書き方は、
「型パラメータ T に String を入れて、T = String として Box を使う」
という意味です。
型パラメータは 1 つでなくてもよい
複数の型パラメータを宣言したいときは、カンマ区切りで並べます。
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
Javaここでは <K, V> が型パラメータの宣言です。
「Pair は K と V という 2 種類の型パラメータをとるクラスです」
「クラスの中では K・V を型名のように自由に使えます」
という約束になります。
使う側ではこうなります。
Pair<String, Integer> p = new Pair<>("age", 30);
String key = p.getKey(); // String
Integer value = p.getValue(); // Integer
Javaメソッドの型パラメータ宣言をかみ砕く
戻り値の前の <T> が「メソッドの型パラメータ宣言」
メソッドにも独自の型パラメータを宣言できます。
例として「配列をそのまま List に変換するメソッド」を考えます。
import java.util.ArrayList;
import java.util.List;
public class GenericMethods {
public static <T> List<T> toList(T[] array) {
List<T> list = new ArrayList<>();
for (T e : array) {
list.add(e);
}
return list;
}
}
Javaここで注目するのは、public static のすぐ後ろにある <T> です。
public static <T> List<T> toList(T[] array)
この <T> が、「このメソッドは T という型パラメータを 1 つ受け取ります」という宣言です。
クラス宣言との違いは、
クラス:class Box<T> の <T> は「クラスの外側」に付く
メソッド:public static <T> ... の <T> は「戻り値の型の前」に付く
という位置だけです。
このメソッドを呼ぶときは、だいたいこんな感じになります。
String[] names = {"Alice", "Bob"};
Integer[] nums = {1, 2, 3};
List<String> nameList = GenericMethods.toList(names); // T は String と推論
List<Integer> numList = GenericMethods.toList(nums); // T は Integer と推論
Javaコンパイラが、渡した配列の型から T を推論してくれます。
クラスの型パラメータとメソッドの型パラメータは別物になれる
クラス自体がジェネリクスでも、その中のメソッドが「さらに」型パラメータを持つこともあります。
public class Wrapper<T> {
private T value;
public Wrapper(T value) {
this.value = value;
}
public T get() { return value; }
public <U> U convert(java.util.function.Function<T, U> mapper) {
return mapper.apply(value);
}
}
Javaここでは、クラスに T、メソッドに U という型パラメータがあります。
class Wrapper<T> の <T> は「クラスの型パラメータ宣言」public <U> U convert(...) の <U> は「メソッドの型パラメータ宣言」
として、スコープも役割も別です。
型パラメータ名の付け方(T / E / K / V など)
意味のある一文字を使うのが慣習
型パラメータ名には、実は何でも使えます。
public class Box<HOGE> {
private HOGE value;
public void set(HOGE value) { this.value = value; }
public HOGE get() { return value; }
}
Javaこれでも動きますが、現場ではほとんど見かけません。
T:Type(汎用的な 1 つの型)
E:Element(コレクションの要素)
K, V:Key / Value(マップのキーと値)
R:Return(戻り値の型)
例えば、標準ライブラリでは List<E> や Map<K, V> という宣言がよく出てきます。
自分のコードで書くときは、
Box<T>:中身の型は何でもよい箱Repository<T>:保存対象のエンティティ型Pair<K, V>:キーと値のペア
のように、「役割から連想しやすい一文字」を選ぶと読みやすくなります。
「宣言」と「指定」を分けて理解する
宣言側:型パラメータを“受け取れるようにする”場所
ここまで出てきた <T> や <K, V> は、すべて「宣言」です。
class Box<T> { ... }public static <T> void print(T value) { ... }
この段階では、まだ具体的な型は決まっていません。
「ここに型を差し込めるようにしておきます」という“型の穴あけ”だけをしている状態です。
利用側:型パラメータに“具体的な型をはめる”場所
「宣言された型パラメータ」に、実際の型を対応させるのが利用側です。
Box<String> box = new Box<>();
box.set("hello"); // T は String
String s = box.get();
JavaBox<String> と書いた瞬間に、「T = String」と決まります。
同じ Box というクラスでも、
Box<Integer> → 「T = Integer」Box<User> → 「T = User」
のように、“型だけが違うバリエーション”として使えるわけです。
ここを「宣言(穴を開ける)」と「指定(穴を埋める)」で分けて理解できると、
ジェネリクスがぐっと整理されます。
最初に意識してほしい型パラメータ宣言のパターン
ここまでを踏まえて、「自分で書けるようになってほしい宣言」をまとめます。
クラスに 1 個の型パラメータを付ける:
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Javaクラスに 2 個以上の型パラメータを付ける:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) { this.key = key; this.value = value; }
}
Javaメソッドに型パラメータを付ける:
public static <T> T first(T[] array) {
return array[0];
}
Javaクラスとメソッドの両方に型パラメータがある:
public class Wrapper<T> {
private T value;
public Wrapper(T value) { this.value = value; }
public <U> U map(java.util.function.Function<T, U> mapper) {
return mapper.apply(value);
}
}
Javaこの 4 パターンを「自分の手で書けるか」を一つの目安にしてみてください。
まとめ:型パラメータ宣言を自分の中でこう整理する
型パラメータの宣言は、
「クラスやメソッドの宣言時に <T> などを書いて、“ここには後で具体的な型を差し込めますよ”と宣言すること」
です。
クラスなら class Name<T, U> { ... } の <T, U>
メソッドなら public <T> T method(T value) の <T>
ここが“型の穴”を開けている場所です。
そして利用側で、Name<String, Integer> のように具体的な型をはめることで、
「同じクラス・メソッドを、型だけ変えて再利用する」ことができます。
