Java 入門

ホーム > ジェネリックス > ジェネリックスとは?なぜジェネリックスなのか

ジェネリックスとは?なぜジェネリックスなのか

なぜジェネリックスなのか?

ジェネリックスはどのような問題を解決するものなのでしょうか?

それを理解するために、次のような箱 (Box) クラスを考えましょう。

public class Box {

  Object o;
  
  public Box(Object o){
    this.o = o;
  }
  
  public Object get(){
    return o;
  }
  
}

これは単なる箱 (Box) を表すクラスです。コンストラクタで何かオブジェクトを受け取ったら、それを内部的に Object 型の o に保持しておき、get メソッドが呼ばれたときにそれを返すだけです。

これを利用するプログラムを考えましょう。

Box b = new Box(123);
Integer i = (Integerb.get();
System.out.println(i);

Box のコンストラクタで 123 という数字を渡し、それを get メソッドで取り出し、Integer にキャストして Integer 型の変数にセットし出力しています。

この実行結果は、次のようになります。

123

確かに 123 という数字が出力されました。

では、もう一つの利用例をみてみましょう。

Box b = new Box("Hello");
String s = (Stringb.get();
System.out.println(s);

この例では Box のコンストラクタで "Hello" という文字列を渡し、それを get メソッドで取り出すときに String にキャストして変数にセットし、それを出力しています。 これを実行すると次のように出力できます。

Hello

最初の例では Box クラスは 123 という整数を扱い、二つ目の例では "Hello" という文字列を扱うことができています。 この箱 (Box) クラスは、いろいろな型のオブジェクトを保存するための「箱」として使うことができています。

ここまでは問題ないように見えます。

では、次の例をみてください。

Box b = new Box(123);
String s = (Stringb.get();
System.out.println(s);

この例ではコンストラクタで 123 を渡していますが、それを取り出すときに String にキャストしています。

コンパイルは成功しましたので、実行してみましょう。すると次のような例外が投げられてしまいました。

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer 
cannot be cast to java.lang.String
	at com.keicode.java.test.GenericsTest2.main(GenericsTest2.java:8)

Integer オブジェクトはそのまま直接キャストして String にはなりませんから、このような例外が発生したのです。

さて、何が問題だったのでしょうか。

不具合があってもコンパイルでき、実行時に失敗する

まず、上記のコードは int を直接 String にキャストするという誤ったコードを書いているわけですから、 これはコンパイル時に誤りを検出できたほうがいいはずです。(一方、存在するはずのファイルが無かった、などの実行時にしかわからないような処理の失敗は実行時の例外発生で致し方ありません)

コードに不具合があるのにコンパイルはでき、実行時に例外が発生するのでは安定したコードを書くのは非常に難しくなります。

それぞれの型毎の実装・・・

結局この箱クラスの get メソッドは、コンストラクタでセットした型のオブジェクトを返さなければならなかったのです。例えば、コンストラクタで Integer 型なら Integer が返る。 String をセットすれば String が返るという動きです。

では、Integer 用の箱 (Box) クラスとして IntegerBox クラス、String 用の箱クラスとして StringBox クラス、Long 用の箱として LongBox ... などと定義していくのでは大変です。

ジェネリックスの登場

そこで登場するのがジェネリックスです。

ジェネリックスでは型を汎用化します。

これがどういうことか、ジェネリックス (Generics) を用いて、上の Box クラスを書きなおしてみましょう。

public class Box<T> {

  T o;
  
  public Box(T o){
    this.o = o;
  }
  
  public T get(){
    return o;
  }

}

今まで Object 型としていた箇所が T で置き換えられ、クラス名に <T> という指定が追加されています。

class Box<T> とすることで、「この Box クラスは型 T に対するクラスですよ」と定義していることになります。

Integer 用の IntegerBox、String 用の StringBox・・・、などと型違いで同様のクラスを複数定義する代わりに、ジェネリックスを使って Box を定義しておけば、 それぞれ、Box<Integer>、Box<String> のようにそれぞれの型用の Box クラスを用意することが可能となります。

これを使う側は次のようなコードになります。

Box<Integer> b = new Box<Integer>(new Integer(123));
Integer i = b.get();    
System.out.println(i);

この Box はそれぞれの型専用の Box になりますから、キャストをする必要もなくなり、Int の Box に String を入れてしまうというような誤りはコンパイル時に検出できることになります。

このジェネリックスを用いたクラス定義、使い方については別のページで説明します。

ホーム > ジェネリックス > ジェネリックスとは?なぜジェネリックスなのか