環境情報学科 オブジェクト指向設計論 (環境情報学科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の異なるバージョンを作成しなければならない。
オブジェクトを格納する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() ); |
(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(); などとしたとき。)
以上の諸問題を解決するのが,ジェネリッククラスである。