環境情報学科 オブジェクト指向設計論 (環境情報学科3年次前期配当 月曜日3限)


●ジェネリッククラス

 Javaでは,JDK 1.5.0 と呼ばれるバージョンから,「クラスの定義を複数のデータ型について使い回す
ことが出来る」ジェネリッククラス(generic class)が導入された。現在のコレクションクラスは,この
ジェネリッククラスを使っているので,まずこのジェネリッククラスから紹介する。

○あるデータ型を専用に扱うクラス

 次のクラス MyArray は,配列が自動で大きさを調整できるように作成した高機能版配列である。これは,
int型データを格納できるようにしてある(下図の赤字部分)。
MyArray.java

しかし,上図のクラスMyArrayはint型データ専用であるため,double型の値やクラス型のオブジェクトなど
は格納できない。たとえば,double型のデータを格納するMyArrayは別に定義しなければならない(下図)。
double用に書き直した部分は赤字で書かれている
(もちろん,上図のint型用MyArrayと併用するなら,クラスの名前もMyArrayDoubleなどとする必要がある)


オブジェクトを格納するMyArrayを考える場合も事情は同じである。例えばA型クラスのオブジェクトを格納する
ためのクラスとしてMyArrayを下図のように書き直さなければならない。A型用に書き直した部分は赤字で書か
れている。


しかしこれでは,A型オブジェクト専用になってしまい,別のクラスのオブジェクトを格納する場合にはやはり
MyArrayの異なるバージョンを作成しなければならない。

○どんなクラス型のオブジェクトでも一緒に扱えるクラス(Object型オブジェクトを扱うクラス)

オブジェクトを格納するMyArrayについては,「JavaにはすべてのクラスのスーパークラスObjectが用意
されている」ことを利用して,Objectを格納するMyArrayを考えることができる(下図)。
Object型用に書き直した部分は赤字で書かれている。
このObject型用のMyArrayは,「どんなクラスのオブジェクトもObject型オブジェクトの一種である」ので,
どんなクラスのオブジェクトでもObject型として格納できる。下図では,main()メソッドの1〜3行目で

  MyArray ma = new MyArray( 3 );
  ma.add( new A() ); ma.add( new B() );
  for( int i = 0; i < 8; i++ ) ma.add( new B() );

としているように,MyArray型オブジェクトmaにA型オブジェクトとB型オブジェクトを格納している。

このObject型用MyArrayは,様々な型のオブジェクトをObject型として登録してしまうので,いったん格納した
オブジェクトを取り出す際には,「Object型(スーパークラス)→特定のクラス型(サブクラス)」というデータ型 の
変換(型変換)をしなければならない
。これは is-a 関係による「サブクラス→スーパークラス」(サブクラス 型オブ
ジェクトをスーパークラス型オブジェクトの一種とみなして,スーパークラス型へ変換)という自動で行 われる変換
と異なり,通常は許されないし,自動で行われることもない。

この,通常は許されない「スーパークラス→サブクラス」の型変換を行うには,上図のmain()メソッドの最後の
部分で
		 (A) ma.elementAt( 0 )
		 (B) ma.elementAt( 1 )

と使用されている例のように
(サブクラス名) スーパークラス型の値


とする。これを型キャストと呼ぶ。スーパークラスからサブクラスへの型キャストをダウンキャスト と呼ぶ。

●基本型(プリミティブ型をオブジェクトとして扱う) 〜 プリミティブ型用ラッパークラス

 このObject型を扱うMyArrayは,クラス型のインスタンス,すなわちオブジェクトならなんでも格納できるが,int型
やdouble型などの基本型(プリミティブ型)は格納できない。こういったときのために,各基本型をオブジェクトとして扱
えるようにするための特別なクラス型がJavaには用意されている(下図)。たとえば,int型用にIntegerというクラスが用
意されている。

 基本型 
 対応するクラス型 
 内部の値を取り出すアクセッサメソッド(ゲッタ) 
byte
 Byte
 byte	  byteValue();
	short
 Short
 short  shortValue();
int
 Integer
 int    intValue();
long
 Long
 long   longValue();
char
 Character
 char   charValue();
float
 Float
 float  floatValue();
double
 Double
 double	doubleValue();

これらのクラスのオブジェクトは,

 Integer obj_i = new Integer( 10 );  // Integer型のオブジェクトobj_iに10という値がセットされた
 int j = obj_i.intValue();           // intValue()メソッドで,格納しているint型の値を取り出せる
というように,コンストラクタで基本型の値を設定し,特定のメソッドで値を取り出せるようになっている。
この状況を図にすると以下のようになる。


●ラッパー (wrapper)
このInteger型クラスの働きをもう模式化してみると下図のようになる。


この「ラッパー」という考え方は,ソフトウェアの設においては非常に重要な考え方である。
Integer型のような基本型用ラッパーを用いれば,基本型の情報をMyArrayに登録することが
可能である(下図)。
MyArray.java

●Object型用MyArrayの問題点

Object型用のMyArrayは一見便利そうではあるが,以下のような問題がある。
問題1)いったん格納されるとObject型として格納されるため,その後に格納されたオブジェクトを取り出すときには,
   オブジェクトの本当(元)の型がわからなくなってしまう。
   例)MyArray型のオブジェクトmaから要素を取り出しても,その要素はObject型であるということしかわからない。
問題2)わかったとしても,「Object型(スーパークラス)→特定のクラス型(サブクラス)」という
   通常は許されない型変換をしなければならない という問題が出る。

・問題1)については,次のような特定の型用のラッパー版MyArrayを考えることが出来る。しかし,様々な型について
 ラッパーを作成しなければならないために,かなり手間はかかってしまう。

class MyArrayForInteger {
  private MyArray ma = null;
  public MyArrayForInteger( int num ) {
   ma = new MyArray( num );
  }
  public Integer elementAt( int n ) {
    return (Integer) ma.elementAt( n );
  }
  public void add( Integer i ) {
    ma.add( i );
  }
}


・問題2)についてだが,一般にダウンキャストは多用すべきではない,とされている。
 なぜなら,サブクラスのオブジェクトをすべてスーパークラスのオブジェクトとして扱える(サブクラス→
スーパークラスへの自動変換のおかげ)からこそ,処理の多くをスーパークラス型で記述することで,多くの
サブクラスのオブジェクトをまとめて処理できる,というオブジェクト指向の最大の美点が活かせる
のである。
逆にスーパークラスのオブジェクトを元のサブクラスのオブジェクトへ強制的に変換することは,ふたたびオ
ブジェクトの具体的な型ごとに処理を分けて書かなければならなくなり,オブジェクト指向の美点を活かせない
ことになってしまう。
(また,正しい型にダウンキャストしないと例外ClassCastExceptionが発生してしまうと言うリスク
もある。例えば,A型とB型がis-a関係にないにもかかわらず,A a = (A) new B(); などとしたとき。)

以上の諸問題を解決するのが,ジェネリッククラスである。

○ジェネリッククラス(いろいろなデータ型のために使い回せるクラス定義)

 いままで挙げた問題点を整理すると,次のようになる。

 ・便利なクラスは,いろいろなデータ型のために用意したいが,ほとんど内容は同じにも
  かかわらずいちいち定義を新たに描かなければならない。
 ・Object型を扱うようにすればいろいろなオブジェクトを扱えるが,オブジェクト指向の
  良さを失わせるダウンキャストが必要となり,使い方を間違えると例外が発生する。

これらの問題を解決するのが,ジェネリッククラス(generic class)である。

ジェネリッククラスのアイディアは,『仮の型』を使ってクラスを定義しておき,その定義を様々な『実際の
型』用に使い回す,というものである
。では,具体的に見ていこう。

次のT型用MyArrayのクラス定義を見て欲しい。ここで前に例に挙げたObject用のMyArray定義と比較して
みると,ObjectのかわりにTが使われているのはもちろんだが,クラス名がMyArrayではなく,MyArray<T>
となっているところが特徴的である。他の部分は,前に例に挙げたA型用MyArrayと同じである(数カ所で
Objectが残っているところがあるが,これについては後で説明する)。
MyArray.java (ジェネリッククラス版)

(※上のMyArra.javaをダウンロードし,コンパイルせよ。なお,コンパイル時に警告が出るがここでは無視せよ)
実は,このMyArray<T>のように,<>つきで定義されたクラスは,ジェネリッククラスと呼ばれる。<>
の中のTは『仮の型』で,このMyArray<T>は,「取り扱う型を仮にTとしてMyArrayを定義したクラス」
と言える。(なお,ファイル名は MyArray<A>.java とせずに,<…>の部分を省いた MyArray.java とする。)

そして,このMyArray<T>クラスの定義は,様々な『実際の型』に”使い回せる”のである。次の使用例
を見てみよう。
TestGenericMyArray.java

この例では,なんと『仮のT型用のMyArray<T>』の定義を『A型用のMyArray<A>』,『B型用のMyArray<B>』
として使い回すことができている
。実際にTestGenericMyArray.javaをコンパイルして実行してみよ。

これは,次のような仕組みで可能になっている。『仮のT型用のMyArray<T>』が定義された上でMyArray<T>が
MyArray<A>としてA型用に使用されると,ジェネリッククラスMyArray<T>の定義を参考に,自動的にMyArray<A>
の定義が生成され,それが使用される
のである。MyArray<B>についても同様である。


ジェネリッククラスは便利であるが,いくつか制限がある。たとえば,上のMyArray<T>は

となっていたが,ObjectのところもTに直して

としたいところである。こうすれば,一箇所で行っていたダウンキャストもしなくてすむはずである。
しかし,ジェネリッククラスでは,仮の型(ここではT)の配列は生成できないことになっているので
これは不可能なのである

また,ジェネリッククラスは,クラス型用にしか利用できない。MyArray<int>などは不可能である。




戻る