PR

【Java】ジェネリクス<>とは?初心者向けに1から解説

Java

ジェネリクス(Generics)とは、Javaで変数やクラスの「型をあとから自由に指定できる仕組み」です。以下のようなコードを見て、<>って何?と思ってこのページにたどり着いた方も多いはずですが

ArrayList<String> list = new ArrayList<>();

この「<String>」の部分がジェネリクスです。

スポンサーリンク

ジェネリクスとは?

ジェネリクス(Generics)とは、Javaで使われる「型を指定できる仕組み」のことです。

…でも、これだけだとまだイメージが湧きませんよね。

もう少しわかりやすく言うと、

  • 「この箱には、文字列だけを入れたい」
  • 「あの箱には、数字だけを入れたい」

のように、「扱えるデータの種類をあらかじめ指定することができる」のがジェネリクスの基本的な考え方です。

ジェネリクスが登場した理由とメリット

昔のJavaにはジェネリクスがありませんでした。そのため…

  • 箱に色々な種類のデータが混ざってしまう
  • 取り出すときに「これって何型だったかな?」とキャスト(型変換)が必要で面倒だった

そこで登場したのがジェネリクス。メリットは3つあります。

メリット意味効果
型安全決めた型しか入れないようにする間違った型を入れられない
キャスト不要型が決まっているので変換不要コードが短くなりスッキリ
読みやすい一目で型がわかる他の人がコードを見た時もわかりやすい

まずはイメージをつかむ

ジェネリクス無しの世界😢

[りんご, 100, バナナ, 200, メロン…]
  • 何が入っているか不明
  • 取り出してから「これは何?」と確認が必要

ジェネリクス有りの世界✨

箱A<String>:[りんご, バナナ, メロン]
箱B<Integer>:[100, 200, 300]
  • 最初から型が決まっていて、混ざることがない
  • 安全で取り出しやすい!

そのため、ジェネリクスは、ArrayListHashMap などのコレクション系クラスで最もよく使われます。
コレクションは「複数の値をまとめて持つ」ので、何の型を入れるか決めることにおおきなメリットがあるためです。もちろんコレクション以外でも利用できるシーンはありますが、基本はコレクション系のクラスでよく使える!っていう風に理解しておくとよいでしょう。

ジェネリクスの基本構文

ジェネリクスは次のように書きます。

クラス名<型名> 変数名 = new クラス名<型名>();
  • クラス名:ArrayListやHashMapなど
  • 型名:StringやIntegerなど扱う型

例えば、「文字列だけを入れる箱」を作るには…

ArrayList<String> fruits = new ArrayList<String>();

これが基本形です。(最近のJavaなら右辺の型は省略可能!)

ArrayList<String> fruits = new ArrayList<>();

ジェネリクスのよくある使い方

ジェネリクスを利用すると、リストから要素を取り出す際に明示的なキャストが不要です。

List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);

for (Integer num : intList) {
    System.out.println(num);
}

この例では、intList に格納される値は常に Integer 型であることが保証されているため、ループ内でキャストする必要がありません。また、誤った型の要素がリストに含まれることを防ぐことができます。

もし、ジェネリクスを使わずに List を定義すると、以下のように明示的なキャストが必要になります。

List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0); // キャストが必要

このようなコードでは、リストに異なる型の値が混在する可能性があり、実行時に ClassCastException が発生するリスクがあります。ジェネリクスを使用することで、上記の問題を解決できます。以下のように書き換えることで、型安全性が向上します。

List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // キャスト不要

この場合、リストには String 型の値のみを格納できるため、キャストが不要になります。また、型が保証されるため、誤った型を扱うことによるエラーを未然に防ぐことができます。

【応用編】自分でジェネリクスクラスを作る方法

ジェネリクスはコレクションだけでなく、独自クラスにも適用できます。例えば、以下のような汎用的なクラスを定義する例を見てみましょう。

class Box<T> {
    private T data;

    void set(T data) { this.data = data; }
    T get() { return data; }
}

Box<String> box = new Box<>();
box.set("Hello");
System.out.println(box.get()); //"Hello"

このコードは「Boxというクラスの中で、あとから使う型(T)を自由に指定できるようにしている」という仕組みです。※ T というのは、仮の型名(型のプレースホルダー)です。

  • Box<T>:これは「このBoxはT型のデータを扱います」という意味。
  • private T data;:データを入れる変数 data は、T型ですよ〜 という意味。
  • set(T data):引数として T型のデータ を受け取って、保存。
  • get():保存された T型のデータ を返す。

つまり「このクラスはTという型を扱う、型付きの箱(Box)」ということ。Box<String> と書いた時点で「T = String」と明確に決まるので、String型のメソッドが自由に使えるようにもなります。

Box<String> box = new Box<>();
box.set("こんにちは");

String value = box.get();
System.out.println(value.length());      // 出力: 5
System.out.println(value.toUpperCase()); // 出力: コンニチハ
System.out.println(value.contains("にち")); // 出力: true

ジェネリクスを使用することで、型の安全性を向上させ、明示的なキャストを減らすことができます。これにより、コードが簡潔になり、バグの発生を防ぐことができます。特にコレクションを扱う際は、ジェネリクスを積極的に活用しましょう。

Q
List<>と class A<> の違いが直感的に理解できない・・・
A

はい、「書き方は似ていても、役割・立場が違う」という点で、List<>class A<> は少し別物と考えるのは正しいです。

✅ 結論:

  • List<E>「使う側(利用者)が型を指定するクラス」
  • class A<T>「作る側(設計者)が型をあとから入れられるように設計するクラス」

🔍 順を追って説明

List<String> の意味は?

List<String> names = new ArrayList<>();

→ これはすでにジェネリクスで作られているクラスを「使う側」の立場で使っています。

  • List<E> は Javaの標準ライブラリで定義されたインターフェース。
  • E は「Element(要素)」の意味。
  • List<String> というのは、「このリストにはString型しか入れません」という制約を**「使うときに」指定している**。

class A<T> の意味は?

class A<T> {
    T value;
}

→ これは「クラスを作る側」の視点です。

  • 「このクラスはどんな型でも扱えるように設計しておきたいな」
  • 「使うときに好きな型を入れてもらおう」
  • → そういうときに「型パラメータ T」を使う

つまり、型を自由に差し込める「テンプレート」として class A<T> を作っているわけです。


📌 まとめ表

比較項目List<String>class A<T>
立場使う側作る側
目的決められた型パラメータに具体的な型を入れる型パラメータを用いて柔軟なクラスを設計する
役割ジェネリクスクラスの「利用」ジェネリクスクラスの「定義」
型はいつ決まる?宣言時に決める(例:List<String>使うときに決まる(例:new A<Integer>()
タイトルとURLをコピーしました