re:annkara

日々学んだことを書き留めておく。

ジェネリクス入門

Javaジェネリクスについて、自分なりにまとめておきたいと思う。
謝り等ありましたらご指摘いただけると幸いです。

ジェネリクスが使えると何が良いか??

いきなりだけど、ジェネリクスのメリットを考えて見る。
例えば、オブジェクトを保持する適当なクラスは以下のとおり。

public class Box {

    private Object obj;

    public void add(Object arg){
        this.obj = arg;
    }

    public Object get(){
        return this.obj;
    }
}

このBoxクラスをインスタンス化する場合、Objectクラスなのであらゆるオブジェクトを扱うことができる。
あらゆるオブジェクトを使える一方で、StringやInteger、もしくはユーザ定義したクラスのオブジェクトなどを扱う際には、 getメソッドを呼び出す際に、必ずキャストしなければならない。

Box box = new Box();  
box.add("文字列を格納");  
String str = (String) box.get();  

このキャストを忘れてしまうと、実行時にClassCastExceptionが発生してしまう。
ここで問題となるのは以下の2点になる。
1. プログラマにキャストを強制する。 → キャスト忘れちゃうかも。
2. 実行時されるまでこのクラスを利用した処理が成功するかわからない。 → コンパイル時にわかった方がよくない??

だからといって、文字列を扱うStringBoxクラス、Integerを扱うIntegerBoxクラス、ユーザ型を扱うUserBoxクラス、、、、などなどと、 各オブジェクトに対応するクラスを作成するのはなかなかに辛さがある。
ここで、任意の型を扱うBoxクラスというのを定義できたらいいんじゃない??というわけででてきたのが、ジェネリクスである。
上のBoxクラスをジェネリクス型に置き換えると以下のとおり。

public class Box<E> {

    private E obj;

    public void add(E arg){
        this.obj = arg;
    }

    public E get(){
        return this.obj;
    }
}

クラス宣言に<E>を付け加えて、Objectと宣言していたところを単にEで置き換えただけだけど、こうするだけでキャスト不要で、 コンパイル時に型チェックが入る任意のBoxクラスを作ることができた。
ジェネリクスがあれば、キャスト処理も特有のクラス定義も不要で、安全で柔軟な型を定義することができる,というのが個人的にはメリットだと考えている。

ジェネリクス周りの用語の整理

<E>の表記が登場したので、ここでジェネリクス周りの用語を整理しようと思う。
用語の一覧はEffective Javaの項目23から抜粋。

  1. ジェネリクス
    Box<T>

  2. 仮型パラメータ、仮型変数
    T
    通常大文字1文字で表現されることが多い。名前自体はなんでも良いが、TはType、KはKeyを表すなどの慣習は存在する。

  3. パラメータ化された型、ジェネリクス型の呼び出し
    Box<String>
    型パラメータに具体的な型 (Stringなど)を代入することで、その型の役割( どの型を扱うことができるか)を決める程度に理解している。

  4. 実型パラメータ、実型変数
    String など
    仮型パラメータに代入される具体的な型。

  5. raw型
    Box
    ジェネリクス型を使用しない型のこと。特段の事情がない限り、raw型を使うべきではない。

ワイルドカードや境界型パラメータなど、まだ出てきていない概念については後述しようと思います。
思いの外長くなってしまったので、一旦ここまでで。