Java Genericsの裏側を知る

文:Peter V. Mikhalenko(special to TechRepublic)
翻訳校正:原井彰弘
2007/12/14 18:04

Java 1.5で追加されたGenericsについて、その使い方と効果を解説する

可変長引数

CやC++にはあったもののバージョン1.5以前のJavaにはなかったもう一つの便利な機能は、可変長引数である。複数の引数が、プリミティブな配列型として扱われるのである。mainメソッドに含まれている呼び出しは、すべてまったく同じメソッド宣言を呼び出しているものであり、コードとして正しい。以下の例を見て、新しい構文を理解していただきたい。

public static void showMessages(String ... args){
for(String arg : args)
System.out.println("Message: "+arg);
}
public static void main (String ... args){
showMessages("hello world");
showMessages("potatoes", "tomatoes");
showMessages("mother", "sister", "father");
showMessages("Ryan", "Lacy", "Tara", "Chad");
}

ワイルドカード

JavaのGenericsでの型パラメータは、特定のクラスに限定されるものではない。Javaでは、ワイルドカードを用いることによって、ジェネリックオブジェクトの型パラメータが取り得る型の範囲を定めることができる。ワイルドカードとは「?」で表される型パラメータで、多くの場合型の範囲の制約と共に用いられる。ワイルドカードが指定されたオブジェクトについて正確な要素の型が分からない場合、そのオブジェクトのメソッドが扱う型には制限が加えられる。

ここでは、制約が何もないワイルドカードの例としてList<?>を考えてみよう。List<?>はオブジェクト型が不明なリストを表す。List<?>を引数として受け取るメソッドは、型パラメータの値にかかわらずどのような種類のリストでも受け取ることができる。ただし、List<?>を用いた場合、リストから読み取りを行った際にはObject型のオブジェクトが常に返される。また、型パラメータが不明なので、null以外の要素をリストに書き込むことはできないようになっている。

ジェネリックな要素の型の上限を指定するには、extendキーワードが用いられる。extendを用いると、ジェネリック型が特定のクラスのサブタイプである -- そのクラスを継承したクラスか、そのインタフェースを実装したクラスである -- という制約を課すことができる。つまり、List<? extends Number>の場合は、リストはNumberクラスを継承したオブジェクトを要素として含むという意味になる。これは、具体的にはリストはList<Float>やList<Number>でありうるという意味である。このケースでは、リストからの読み取りを行った場合にはNumberオブジェクトが返される。一方、リストにnull以外の要素を追加することはできないようになっている(※訳注:原文では「リストにnullの要素を追加することはできない」となっておりますが、正しくは「リストにnull以外の要素を追加することはできない」です)。

ジェネリックな要素の型の下限を指定するには、superキーワードが用いられる。superを用いると、ジェネリック型が特定のクラスのスーパータイプであるという制約を課すことができる。つまり、List<? super Number>はList<Number>もしくはList<Object>を意味することになる。このケースでは、リストからの読み取りを行ったときにはObject型のオブジェクトが返される。一方、リストに書き込みを行う場合は、Number型であればどのような要素でも追加できるようになっている。なぜかというと、Number型がリストに格納される型として正しいことは保証されているからである。

まとめ

Genericsは非常に微妙で難しい問題を含んでいる。Genericsに奇妙な機能が多いのは、コンパイル時に行われる処理であるからという理由が大きい。型消去が行われるため、実行時のコードにはほとんどその痕跡が残らないのである。たとえば、(Genericsを多用して記述されている)ArrayListを逆コンパイルしたところで、クラスファイルにはGenericsの存在を示すものは何一つ残っていないのだ。ただ単に、普通のObject型のオブジェクトを、キャストせずに出し入れしているに過ぎないのである。Genericsが用いられていたことを唯一示しているのは、呼び出し側のgetメソッドに、Object型を実際に受け取る型に変換するキャストが自動的に挿入されているということだけなのである。ArrayList自身にはキャストのコードはまったく含まれていないのだ。このような仕組みになっているため、Genericsに多くの制限があり、T型のオブジェクトを生成したり、オブジェクトをT型にキャストしたりといったあからさまな操作は行うことができない。

Genericsに関するより詳しいリソース

記事の感想やご意見をコメントでお寄せください(CNET_IDログインが必要です)
ログイン パスワードを忘れた方  |  新規登録
  • 2日前のトップ記事
  • 3日前
  • 4日前
  • 5日前
  • 6日前
  • 新着記事
  • 人気記事
  • 特集
  • ブログ