ファイヤープロジェクト
総称クラスを定義してみる
2004-10-07T00:30+09:00   matsu
総称クラスを定義してみた.
まず基本のサンプルを作成してみた.
このサンプルの実行結果を以下に示す.
$> java MyGenericClass
myField = java.lang.String
myField = java.util.ArrayList
myField = java.util.ArrayList
総称型FにStringやArrayListが代入されたことが分かる.このサンプルを順に確認してみる.まずクラス宣言部分.
public class MyGenericClass <GenericType> {
総称型としてGenericTypeを宣言している.以後,終端記号GenericTypeは総称型として機能する.Listでは終端記号をEと一文字としていたが,このように複数文字でもよい(クラス名と同じ要領か?).次にフィールドの宣言.
    private GenericType myField;
もはや普通の型として使用できており,総称型だからどうという記述はない.コンストラクタの引数とメソッド返り値と引数も同様に総称型だからどうという記述はない.
複数の総称型を持つサンプルを作成してみた.
以下実行結果.
$> java MyGenericClass2
myField1 = java.lang.Integer
myField2 = java.lang.Double
myField1 = java.lang.Double
myField2 = java.lang.Float
myField1 = java.lang.Double
myField2 = java.lang.Float
複数の場合はコンマ区切りで指定すればよい.
  public class MyGenericClass2 <GenericType1, GenericType2> {
あとは普通にGenericType1,GenericType2を型として使用できる.ただし以下のメソッドシグニチャは競合する.
    void setField (GenericType1 gt1) {
	myField1 = gt1;
    }

    void setField (GenericType2 gt2) {
	myField2 = gt2;
    }
これはもしGenericType1とGenericType2に同じ型が指定された場合に結果としてシグニチャが競合するからだろうか.以下コンパイルエラー.
$> javac MyGenericClass2.java
MyGenericClass2.java:20: 名前が競合しています。
setField(GenericType1) と setField(GenericType2) は削除後の名前が同じです。
    void setField (GenericType1 gt1) {
         ^
MyGenericClass2.java:24: 名前が競合しています。
setField(GenericType2) と setField(GenericType1) は削除後の名前が同じです。
    void setField (GenericType2 gt2) {
         ^
エラー 2 個
エラーメッセージの「削除後」って何だ?
もう少し複雑なパターンとして,型引数をもつインタフェースを実装するクラスのサンプルを作成してみた.
サンプルのMyGenericClass3では型引数をもつインタフェース
Comparable<T>
を実装する.型引数Tはimplementsする時点で解決可能でなければならないようだ.サンプルでは
public class MyGenericClass3 <GenericType1, GenericType2>
    implements Comparable<....
とあるので,TにはMyGenericClass3で使用する総称型だと宣言されているGenericType1,GenericType2か,(importするなどして)この段階で参照可能となっている参照型(クラス)を指定できる.例えばサンプルの上の部分は
public class MyGenericClass3 <GenericType1, GenericType2>
    implements Comparable<String>
などともできる.この部分の指定によって,インタフェースComparable<T>で宣言されている
int compareTo(T o) 
の実装メソッドの総称型Tも決定される.上の記述で想像できるように,インタフェースの総称型Tを決定した結果,それが実装クラスの総称型になる場合もある.サンプルはそのパターンである.サンプルでは
public class MyGenericClass3 <GenericType1, GenericType2>
    implements Comparable<GenericType2>
と,Comparableの型引数をGenericType2としているので,
    public int compareTo (GenericType2 o) {
という実装をしている.
総称型で宣言されたフィールドに対してメソッド呼出をする場合を考えると,「制約」という言葉のニュアンスとは裏腹に,一番制約がない場合が一番呼び出せるメソッドが少ない.以下のサンプルでは総称型GenericTypeには制約がかかっていない.
このソースはコンパイルエラーとなる.
$> javac MyGenericClass4.java
MyGenericClass4.java:19: シンボルを見つけられません。
シンボル: メソッド shortValue()
場所    : java.lang.Object の クラス
        System.out.println("myField.shortValue() = " + myField.shortValue());
                                                              ^
エラー 1 個
myFieldは総称型GenericTypeで宣言されており,エラーメッセージによるとそのmyFieldはコンパイル時にはObject型として扱われている.これはGenericTypeは参照型でなければならず,さらに制約がないので,GenericTypeは「少なくともObject型ではある」ということしか特定できないためだろう.だからObject型が持たないメソッドshortValue()の呼び出しができない.ここで,「GenericTypeはNumber型のサブクラスでなければならない」という制約をかけてみる(※).
public class MyGenericClass4 <GenericType extends Number> {
今度はコンパイルに成功し,実行できた.
$> java MyGenericClass4
myField = java.lang.Integer
myField.toString() = 1
myField.shortValue() = 1
myField = java.lang.Double
myField.toString() = 5.0
myField.shortValue() = 5
myField = java.lang.Double
myField.toString() = 3.2
myField.shortValue() = 3
このように,総称型の制約というのは,型指定する際の制約であり,定義する側としてはその制約によってむしろできることが増える.すなわち総称型のメンバを使用する場合,Object型に対する操作と同等の使用のみが保証され,それ以上を望むなら,extendsによって総称型に制約をかける.
※ このように総称型の制約はextendsによってかける.
matsu(C)
Since 2002
Mail to matsu