Java | Java 詳細・モダン文法:ジェネリクス – 型パラメータの宣言

Java Java
スポンサーリンク

「型パラメータの宣言」を一言でいうと

型パラメータの宣言は、

クラスやメソッドの“名前の横に” <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;
    }
}
Java

class 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();
Java

Box<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

これでも動きますが、現場ではほとんど見かけません。

一般的な慣習は次のようなものです。 Qiita Note

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();
Java

Box<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> のように具体的な型をはめることで、
「同じクラス・メソッドを、型だけ変えて再利用する」ことができます。

タイトルとURLをコピーしました