Menu
Coddy logo textTech

ジェネリクス入門

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 53/87。

値を保存し、後でそれを取り出すクラスを作成する必要があると想像してください。ジェネリクスがない場合、Integer用、String用、Double用といったように、型ごとに個別のクラスを作成しなければなりません。これはコードの重複を招き、メンテナンス上の悩みの種となります。

ジェネリクスは、型安全性を維持しながら、あらゆる型で動作するクラスやメソッドを記述できるようにすることで、この問題を解決します。クラスを使用する際に実際の型に置き換えられるプレースホルダー(型パラメータと呼ばれます)を定義します。

// ジェネリクスなし - Objectを使用し、キャストが必要
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // 手動でのキャストが必要

// ジェネリクスあり - 型安全、キャスト不要
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // キャスト不要

山括弧 <T> は型パラメータを表します。List<String> と記述すると、このリストには文字列のみが含まれることをコンパイラに伝えていることになります。整数を追加しようとすると、実行時に失敗するのではなく、コンパイラが即座にエラーを検出します。

ジェネリクスには2つの大きな利点があります。明示的なキャストの必要性をなくすことと、実行時ではなくコンパイル時に型エラーを検出できることです。これにより、コードがより安全でクリーンになります。以降のレッスンでは、独自のジェネリッククラスやメソッドを作成する方法を探っていきます。

challenge icon

チャレンジ

簡単

古い方法(キャストを伴う Object の使用)と新しい方法(型安全性を備えたジェネリクスの使用)を比較することで、ジェネリクスの威力を実証するシンプルなストレージシステムを構築しましょう。

その違いを直接確認するために、3つのファイルを作成します:

  • OldBox.java: ジェネリクス以前の方法で値を格納するクラスを作成します。OldBox は Object 型のプライベートフィールド content を持つ必要があります。Object を受け取って格納するコンストラクタと、その Object を返す getContent() メソッドを含めてください。このアプローチは機能しますが、値を取得する際にキャストが必要になります。
  • GenericBox.java: 次に、ジェネリクスを使用した現代的で型安全なバージョンを作成します。GenericBox は、宣言に型パラメータ T を使用する必要があります。T 型のプライベートフィールド contentT 型の値を受け取るコンストラクタ、および T 型を返す getContent() メソッドが必要です。このクラスを使用する場合、キャストは不要です!
  • Main.java: 両方のアプローチを並べて実証します。単語(String)と数値(integer)の2つの入力を受け取ります。

    まず、古いアプローチを使用します。単語を格納する OldBox を作成し、String へのキャストを使用して取得し、次のように出力します:OldBox (with cast): [value]

    次に、ジェネリックなアプローチを使用します。同じ単語を格納する GenericBox<String> を作成し、キャストなしで取得して、次のように出力します:GenericBox (no cast): [value]

    次に、整数で動作するジェネリクスを示します。数値を格納する GenericBox<Integer> を作成し、それを取得して、次のように出力します:GenericBox Integer: [value]

    最後に、空行を1行出力し、続いて次のように出力します:Type safety: Generics catch errors at compile time!

入力は、単語(String)と数値(Stringとしての整数。パースする必要があります)の順で2つ受け取ります。

主な違いに注目してください。OldBox では、(String) box.getContent() のようにキャストし、型が正しいことを願う必要があります。GenericBox では、コンパイラが型を認識しているため、キャストは不要です。これがジェネリクスの核心的な利点、つまりキャストの手間をかけずに型安全性を確保できることです!

チートシート

ジェネリクスを使用すると、型安全性を維持しながら、任意の型で動作するクラスやメソッドを記述できます。これらは、クラスが使用されるときに実際の型に置き換えられる型パラメータ(プレースホルダー)を使用します。

ジェネリクスなし(Objectを使用):

List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // 手動でのキャストが必要

ジェネリクスあり(型安全):

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

ジェネリッククラスの作成:

public class GenericBox<T> {
    private T content;
    
    public GenericBox(T content) {
        this.content = content;
    }
    
    public T getContent() {
        return content;
    }
}

ジェネリッククラスの使用:

GenericBox<String> stringBox = new GenericBox<String>("Hello");
String value = stringBox.getContent();  // キャスト不要

GenericBox<Integer> intBox = new GenericBox<Integer>(42);
Integer number = intBox.getContent();  // 型安全

山括弧 <T> は型パラメータを表します。利点としては、明示的なキャストが不要であることや、実行時ではなくコンパイル時に型エラーが検出されることが挙げられます。

自分で試してみよう

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String word = scanner.nextLine();
        int number = Integer.parseInt(scanner.nextLine());
        
        // TODO: word を格納する OldBox を作成する
        // String にキャストして取得し、次のように出力する: "OldBox (with cast): [value]"
        
        // TODO: word を格納する GenericBox<String> を作成する
        // キャストせずに取得し、次のように出力する: "GenericBox (no cast): [value]"
        
        // TODO: number を格納する GenericBox<Integer> を作成する
        // 取得して次のように出力する: "GenericBox Integer: [value]"
        
        // TODO: 空行を出力し、その後に次のように出力する:
        // "Type safety: Generics catch errors at compile time!"
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン