CodeGym /コース /JAVA 25 SELF /ジェネリクス: なぜ必要か、基本構文

ジェネリクス: なぜ必要か、基本構文

JAVA 25 SELF
レベル 26 , レッスン 4
使用可能

1. 「生の」コレクション(raw types)の問題

少し歴史を振り返りましょう。Java 5 以前のコレクションは「何でも受け付ける」存在でした。Object 型のオブジェクトを保持し、コンパイラはそこに何を入れるかをチェックしません。文字列を入れる? どうぞ。数値? もちろん。ネコ? それも可能です。

// 「生の」コレクション(raw types)の例、Java 5 以前
List list = new ArrayList();
list.add("こんにちは");
list.add(42);
list.add(new Object());

問題は取り出して使うときに表面化します:

String s = (String) list.get(0); // OK、これは文字列
String s2 = (String) list.get(1); // ドカン! ClassCastException

コンパイラは黙ったまま、ランタイムで ClassCastException が発生します。まるで「りんご」と書かれた箱の中に、カップとバナナとハリネズミが入っているようなものです。

なぜ良くないのか?

  • エラーは実行時にしか発覚しません。
  • 型が混乱します: 必要な型へ手作業でキャストする必要があります。
  • コードの可読性と安全性が低下します。

解決策 — ジェネリクス(総称型)

Generics(ジェネリクス)は、型パラメータを持つクラス、インターフェイス、メソッドを作成できる仕組みです。つまり、コレクションに「文字列だけを保存して」と指示でき、コンパイラがそれを厳密に監視します。

List<String> words = new ArrayList<>();
words.add("こんにちは");
words.add("世界");
// words.add(42); // コンパイルエラー!List<String> に int は追加できない

これでコンパイラは文字列以外をリストに入れさせません。エラーはプログラムを起動する前に捕捉されます。

ジェネリクスの主眼:
コレクション(に限らず)を型安全にし、エラーをランタイムではなくコンパイル時に検出すること。

2. ジェネリクスの文法: コードではどう書くか

山括弧で型を指定する

コレクションを作成するときは、<> の中に要素の型を指定します:

List<String> names = new ArrayList<>();
names.add("アリス");
names.add("ボブ");
// names.add(123); // エラー: 文字列のリストに数値は追加できない

String first = names.get(0); // キャストは不要!

定番の形:

  • List<String> — 文字列のリスト
  • List<Integer> — 整数のリスト
  • Set<Double> — Double の集合
  • Map<String, Integer> — キーは String、値は Integer

なぜ単に List と書いてはいけないのか?

書けますが、ジェネリクスの利点をすべて失い、コンパイラが警告します:

List list = new ArrayList(); // raw type — 推奨されません!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // ClassCastException の登場!

現代の Java コードは常にジェネリクスを使います。

ダイアモンド演算子 <>

Java 7 以降は、右辺の型が文脈から分かる場合は省略できます:

List<String> list = new ArrayList<>(); // コンパイラがここは <String> だと推論してくれる

3. 各種コレクションでのジェネリクス

ListSetMap の例

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);

Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("アリス");
uniqueNames.add("ボブ");

Map<String, Integer> ages = new HashMap<>();
ages.put("アリス", 23);
ages.put("ボブ", 31);

独自クラスの例

class Student {
    String name;
    int age;
    // ...
}

List<Student> students = new ArrayList<>();
students.add(new Student());

4. 役立つニュアンス

ジェネリクスの利点

型安全性。 コンパイラが、コレクションに正しい型の要素だけが入るように見張ります。

キャスト不要。 以前: String s = (String) list.get(0);。今は: String s = list.get(0);

より読みやすく堅牢。 ランタイムのサプライズが減ります。

ジェネリクスの制約

プリミティブ型は使えません。 ジェネリクスはオブジェクトに対してのみ機能し、プリミティブ(intdoubleboolean)には使えません。ラッパークラスを使いましょう: IntegerDoubleBoolean

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int は自動的に Integer に変換される(オートボクシング)

型消去(type erasure)の概要

Java のジェネリクスは型消去という仕組みで実装されています。コンパイル後、型パラメータの情報は消去され、ランタイムの JVM はそれが List<String> だったのか単なる List だったのかを知りません。これは後方互換性のためです。

帰結: 具体的な型引数を伴う instanceof によるチェックはできません。

List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // コンパイルエラー!

異なる型の要素を追加しようとするとコンパイルエラー

List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // コンパイルエラー: incompatible types: int cannot be converted to String

Map<String, Integer> map = new HashMap<>();
map.put("ネコ", 5);
// map.put(3, "ゾウ"); // エラー: キーは String、値は Integer でなければならない

そしてこれは素晴らしいことです: エラーはコンパイル時に捕捉されます。

コレクションだけではない

ジェネリクスは自作のクラスやメソッドでも使えます。例えば、汎用の「箱」:

class Box<T> {
    private T value;

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

Box<String> stringBox = new Box<>();
stringBox.set("こんにちは");
System.out.println(stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());

コレクションではジェネリクスが標準ですが、Stream APIOptional など他の場所でも目にするでしょう。

5. ジェネリクスでよくある誤り

誤り1: 「生の」コレクションの使用。 List list = new ArrayList(); のような書き方は型安全性を失わせます。常に型パラメータを指定しましょう。例: List<String>

誤り2: プリミティブ型を使おうとする。 List<int> のようには書けません。List<Integer> を使いましょう。

誤り3: コレクションから読むたびに手動キャストする。 ジェネリクスを使っていれば、(String) list.get(i) のようなキャストは不要です。必要になるなら、どこかで型を破っています。

誤り4: 型引数が実行時に利用できると期待する。 型消去のため、instanceofList<String> のようにチェックすることはできません。

誤り5: 1 つのコレクションに異なる型を混在させる。 List<String> と宣言したなら、Integer を追加してはいけません。コンパイラが弾いてくれますし、それで良いのです。

1
タスク
JAVA 25 SELF, レベル 26, レッスン 4
ロック未解除
私のデジタルライブラリのカタログ 📚✍️
私のデジタルライブラリのカタログ 📚✍️
1
タスク
JAVA 25 SELF, レベル 26, レッスン 4
ロック未解除
魔法の万能チェスト 📦✨
魔法の万能チェスト 📦✨
1
アンケート/クイズ
コレクションとgenerics、レベル 26、レッスン 4
使用不可
コレクションとgenerics
コレクションとgenerics
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION