0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Javaのジェネリクス入門 — `<T>`って何?を実務目線で解説

0
Posted at

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の設計力が一段上がります。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?