Javaジェネリクス入門|Boxクラスを作って理解する

Java

List<String>Map<String,Integer> などはよく見かけるが、自分でジェネリクスを定義する機会はそれほど多くない。しかし、簡単なクラスを一度自分で作ってみると、ジェネリクスが何を解決しているのかがかなり見えやすくなる。

お題

1つの値を保存して取り出せる Box クラスを作成せよ。
ただし、次の条件を満たすこと。

  • 同じクラス設計で異なる型を扱えること
  • StringInteger も保存できること

作成

Object型を使う

まずはObjectクラスを使う実装が考えられる

public class Box{
    private Object value;

    public Box(Object value){
        this.value=value;
    }
    public Object getValue(){
        return this.value;
    }
}

アプリケーションクラス内

Box b1 = new Box("John");
String name = (String)b1.getValue();
System.out.println(name);

Box b2 = new Box(100);
Integer num = (Integer)b2.getValue();
System.out.println(num);
    

このようにObjectクラスを用いればどんな型でも格納することができる。
しかし、この実装では値を取り出して利用するときに、

String name = (String)b1.getValue();

のように、毎回ダウンキャストを行わなければならない
また、誤ったダウンキャストを書いてしまっても、コンパイル時にはエラーを検出できず、実行時にエラーが発生する危険がある。
つまり、この実装は型安全ではない

ジェネリクス

そこで2004年、Java5になる際にジェネリクスが導入された。ジェネリクスを使って先程のお題をやってみよう。

public class Box<T>{
    private T value;
    
    public Box(T value){
        this.value=value;
    }
    public T getValue(){
        return value;
    }
}


特徴は、クラス名の後ろに <T> が付いている点である。

この <T>型パラメータと呼ばれ、Box クラスが特定の型に依存せず、さまざまな型の値を扱えることを表している。
たとえば Box<String> と書けば文字列を入れる箱になり、Box<Integer> と書けば整数を入れる箱になる。

private T value; は、このクラスが保持する値を表すフィールドである。
型が T になっているため、String 用にも Integer 用にも、同じクラス定義で対応できる。

コンストラクタ public Box(T value) は、生成時に受け取った値をフィールド value に代入している。
ここでも型は T であるため、Box<String> なら文字列、Box<Integer> なら整数を受け取ることになる。

public T getValue() は、保持している値を取り出すためのメソッドである。
戻り値の型も T になっているため、取り出すときにキャストは不要である。
これが、Object 型で実装した場合との大きな違いである。

たとえば次のように使える。

Box<String> b1 = new Box<>("John");
String name = b1.getValue();
System.out.println(name);

Box<Integer> b2 = new Box<>(100);
Integer num = b2.getValue();
System.out.println(num);
        

このように、ジェネリクスを使うことで、同じ設計のクラスを使い回しながら、型安全に値を扱える ようになる。
つまり Box<T> は、「何でも入れられる危ない箱」ではなく、使うときに型が決まる安全な箱 なのだ。
(TというのはType(型)の頭文字で、一般的に広く使われているが、必ずTでなければならないというわけではない)

例えば

Box<String> b1 = new Box<>("山田太郎");
Integer num = b1.getValue();

のようなミスがあった場合も
Integer に代入しようとした時点で コンパイルエラー として検出できる(エクリプスなどのIDEであれば赤線を出して教えてくれる)

さいごに

ジェネリクスは、利用者として使う場面のほうが圧倒的に多い。
しかし、今回のように一度自分でジェネリクスを使ったクラスを作成してみると、それが何を解決し、どのような便利さをもたらしているのかが見えやすくなる。
普段何気なく使っている機能ほど、一度自分の手で書いてみることが理解への近道である。

Java
スポンサーリンク
シェアする
mjpurinをフォローする

コメント

タイトルとURLをコピーしました