---
title: "Javaのジェネリクス実装｜ジェネリッククラス・境界ワイルドカード・型消去を解説"
url: "https://www.issoh.co.jp/tech/details/3252/"
published: 2024-08-02
updated: 2026-07-17
categories: ["Java"]
publisher: "株式会社一創"
---

# Javaのジェネリクス実装｜ジェネリッククラス・境界ワイルドカード・型消去を解説

Javaのジェネリクスは、Java 5で導入された、クラスやメソッドが扱う型をパラメータ化する仕組みです。コレクションから任意の型を安全に取り出せるようにし、キャストと実行時エラーを減らします。この記事ではJava固有の実装に絞り、ジェネリッククラス・ジェネリックメソッドの書き方、境界ワイルドカード、型消去による制限、プリミティブ型が使えない理由、命名慣習までをコード付きで解説します。ジェネリクスそのものの概念や他言語との違いは[ジェネリクスとは｜型パラメータの仕組みと主要言語での違い](https://www.issoh.co.jp/tech/details/3535/)で整理しています。

## まとめ：Javaジェネリクスの要点

- Java 5以降、`List<String>`のように型を指定してキャストなしで安全に扱える。
- 実装は**ジェネリッククラス（`class Box<T>`）**と**ジェネリックメソッド（`<T> T method(...)`）**の2系統。
- 柔軟性の鍵は**境界ワイルドカード**。読み取りは`? extends`、書き込みは`? super`（PECSの原則）。
- Java固有の制約は**型消去**（実行時に型引数が消える）と**プリミティブ型が使えない**こと。ラッパークラスで扱う。

## Javaがジェネリクスを導入した理由

Java 5より前のコレクションは、要素をすべて`Object`として保持していました。そのため取り出すたびにキャストが必要で、誤った型を入れてもコンパイルは通り、実行時に`ClassCastException`で初めて発覚しました。ジェネリクスは型引数を明示することで、この誤りを**コンパイル時に検出**し、同時に取り出し時のキャストを不要にします。型安全性とコードの簡潔さを両立させたのが導入の目的です。

## ジェネリッククラスの宣言と使い方

クラス名の後に`<T>`と型パラメータを宣言すると、そのクラス内でTを型として使えます。利用側は`<String>`のように具体型を指定します（右辺はダイヤモンド演算子`<>`で省略可）。

```
public class Box<T> {
    private T value;
    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> box = new Box<>();
box.set("hello");
String s = box.get();   // キャスト不要
```

## ジェネリックメソッドの宣言と使い方

メソッド単位で型パラメータを持たせるには、戻り値の型の前に`<T>`を置きます。クラスがジェネリックでなくても、静的メソッドなどに個別に適用できます。

```
public static <T> T firstOf(List<T> list) {
    return list.get(0);
}

Integer n = firstOf(List.of(10, 20, 30));
String s = firstOf(List.of("a", "b"));
```

## 境界型と境界ワイルドカード（PECS）

型パラメータに上限を設けるには`<T extends Number>`と書き、Numberのサブタイプに限定してそのメソッド（`doubleValue()`など）を安全に呼べます。さらに柔軟性を高めるのがワイルドカード`?`です。データを**読み取る側は`? extends`（上限境界）**、**書き込む側は`? super`（下限境界）**を使います。「Producer Extends, Consumer Super」の頭文字を取ってPECSと呼ばれる原則です。逆に、同じコレクションで**読み取りと書き込みの両方を行う場面でワイルドカードを使うのは誤り**で、その場合はワイルドカードではなく具体的な型パラメータ`<T>`を宣言すべきです。

```
public static double sum(List<? extends Number> list) {
    double total = 0;
    for (Number n : list) total += n.doubleValue();
    return total;   // 読み取り専用：? extends
}

public static void fill(List<? super Integer> list) {
    list.add(1);
    list.add(2);     // 書き込み：? super
}
```

## 型消去とその制限

Javaのジェネリクスは**型消去**で実装されており、コンパイル後は型引数の情報が取り除かれます。そのため`List<String>`と`List<Integer>`は実行時には同じ`List`クラスになります。

```
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
boolean same = (a.getClass() == b.getClass());
```

この`same`は`true`になります。型消去の結果、実行時には型引数で区別できません。この制約から、`new T()`によるインスタンス生成、`obj instanceof List<String>`のような型引数を含むinstanceof判定、`T`の配列生成などは行えません。実行時に型で分岐したい場合は、`Class<T>`を引数で受け取るなどの回避策を使います。

## プリミティブ型が使えない理由

ジェネリクスの型引数には`int`や`double`といったプリミティブ型を直接指定できません。`List<int>`は書けず、`List<Integer>`のようにラッパークラスを使います。これは型消去でTが`Object`に置き換わる設計上、Objectを継承しないプリミティブ型を保持できないためです。実際には**オートボクシング／アンボクシング**により`list.add(1)`のように書けますが、大量要素では`int`と`Integer`の変換コストが無視できない点に注意します。

## 型パラメータの命名慣習

型パラメータ名は自由ですが、可読性のため1文字の慣習的な名前が広く使われます。役割が名前から伝わるため、独自の長い名前より慣習に従うほうが読み手に親切です。

| 記号    | 意味          | 主な使用箇所            |
| ----- | ----------- | ----------------- |
| E     | Element（要素） | コレクション（`List<E>`） |
| T     | Type（型）     | 汎用的な型             |
| K / V | Key / Value | マップ（`Map<K,V>`）   |
| N     | Number（数値）  | 数値系の型             |
| S, U  | 2番目・3番目の型   | 複数型パラメータ          |

## Java SE標準ライブラリでの総称型

Java標準ライブラリはジェネリクスを全面的に採用しています。コレクションフレームワークの`List<E>`や`Map<K,V>`はもちろん、Stream APIの`Stream<T>`、値の有無を表す`Optional<T>`もすべて総称型です。日常的に使うこれらのAPIがジェネリクスで設計されているため、自作クラスでも同じ流儀に合わせると一貫性のあるコードになります。

## よくある質問

### ジェネリクスの型引数にintは使えますか？

直接は使えません。`List<int>`は不可で、ラッパークラスの`List<Integer>`を使います。オートボクシングにより値の出し入れは`int`のように書けますが、内部ではInteger型として扱われます。

### 「? extends」と「? super」はどう使い分けますか？

データを取り出す（読む）側では`? extends`、データを入れる（書く）側では`? super`を使います。頭文字を取ったPECS（Producer Extends, Consumer Super）が判断の目安です。読み書き両方を行うなら、ワイルドカードではなく具体的な型パラメータ`<T>`を使います。

### 型消去があるとリフレクションで型引数を取得できませんか？

ローカル変数の型引数は実行時に取得できません。ただし、フィールドやメソッドのシグネチャに書かれた型引数はメタ情報として残るため、`getGenericType()`系のAPIで取得できる場合があります。実行時に型で分岐したい設計では、`Class<T>`を明示的に受け渡す方法が確実です。

### ジェネリクスはパフォーマンスに影響しますか？

型消去のため、ジェネリクス自体による実行時オーバーヘッドはほとんどありません。ただしプリミティブ型をラッパーで扱う際のボクシングコストは発生します。大量の数値処理では、プリミティブ特化のAPI（`IntStream`など）の利用を検討します。

## 関連記事

- [ジェネリクスとは｜型パラメータの仕組みと主要言語での違い](https://www.issoh.co.jp/tech/details/3535/)
- [Javaのガベージコレクション（GC）とは｜仕組み・種類・チューニングの基礎](https://www.issoh.co.jp/tech/details/3084/)
- [カプセル化とは何か？オブジェクト指向における基本概念を解説](https://www.issoh.co.jp/tech/details/6001/)
- [継承とは？オブジェクト指向での使いどころ・委譲との使い分け・アンチパターン](https://www.issoh.co.jp/tech/details/2724/)

---

出典: [Javaのジェネリクス実装｜ジェネリッククラス・境界ワイルドカード・型消去を解説](<https://www.issoh.co.jp/tech/details/3252/>)（株式会社一創）
