Java

汎用型

Language の目次


要素を Collection から取り出すときは、要素をコレクションに格納されている要素の型にキャストする必要があります。これは不便で、しかも安全ではありません。コンパイラはキャストがコレクションの型と同じであるかどうかをチェックしないため、実行時にキャストが失敗するおそれがあります。

汎用型を使用すると、コレクションの型をコンパイラに通知できるため、型のチェックを行うことができます。コンパイラがコレクションの要素の型を認識すると、そのコレクションを矛盾なく使用していることをチェックでき、コレクションからの値の適切なキャストを挿入できます。

既存のコレクションのチュートリアルから、簡単な例を示します。

// Removes 4-letter words from c. Elements must be strings
static void expurgate(Collection c) {
    for (Iterator i = c.iterator(); i.hasNext(); )
      if (((String) i.next()).length() == 4)
        i.remove();
}

同じ例を、汎用型を使用するように変更したコードを次に示します。

// Removes the 4-letter words from c
static void expurgate(Collection<String> c) {
    for (Iterator<String> i = c.iterator(); i.hasNext(); )
      if (i.next().length() == 4)
        i.remove();
}

コード <Type> の箇所は、「Type の」と読み替えてください。上記の宣言は、「String cCollection」と読みます。汎用型を使用したコードの方がわかりやすく、安全です。安全でないキャストや、不要な括弧は取り除かれました。より重要な点として、メソッドの仕様記述をコメント部から署名部に移動しました。そのため、型の制約が実行時に侵害されないことを、コンパイル時に検証できます。プログラムは警告なしでコンパイルされるため、実行時に ClassCastException がスローされることは絶対にありません。汎用型を使用することにより、特に大規模なプログラムでは、可読性および堅牢性が向上するという実質的な効果があります。

「Generics Specification Lead Gilad Bracha」を簡単に説明すると、型 Collection<String>c を宣言すると、変数 c を使用するときはいつでもどこでも true であり、それをコンパイラが保証する (プログラムは警告なしでコンパイルされる) ということです。一方、キャストの場合は、コードのある一時点ではプログラマが考えたことが正しいため、仮想マシンは、実行時にだけプログラマが正しいかどうかを確認します。

汎用型の主な用途はコレクションですが、ほかにも多くの使い方があります。WeakReferenceThreadLocal などのホルダークラスはすべて汎用化され、汎用型を利用できるように改良されました。クラス Class も汎用化されています。Class リテラルは「型トークン」として機能するようになったため、実行時とコンパイル時両方の型情報を提供できます。これにより、新しい AnnotatedElement インタフェースの getAnnotation メソッドのように、静的ファクトリの形式を利用できるようになります。

    <T extends Annotation> T getAnnotation(Class<T> annotationType); 
これは汎用メソッドです。型パラメータ T の値を引数から推測して、次の例のように T の適切なインスタンスを返します。
    Author a = Othello.class.getAnnotation(Author.class);
汎用型の前に、結果を Author にキャストする必要があります。また、Annotation のサブクラスを表す実際のパラメータをコンパイラがチェックする方法はありません。

汎用型は「型消去」によって実装されます。汎用型情報は、コンパイル時にしか存在せず、コンパイル後はコンパイラによって「消去」されます。このアプローチの主な利点としては、汎用コードと、パラメータ化されていない型 (技術的には「raw」型と呼ばれる) を使用するレガシーコードとの間に、総合的な相互運用性が実現する点です。主な欠点としては、パラメータの型情報を実行時に利用できない点と、動作が適切でないレガシーコードと相互運用すると、自動的に生成されたキャストが失敗するおそれがある点です。しかし、動作が適切でないレガシーコードと相互運用するときも、汎用コレクションに対して実行時の型の安全性が保証されます。

java.util.Collections クラスは、実行時の型の安全性を保証するラッパークラスに装備されました。同期化され変更不可能なラッパーと似た構造を持ちます。これらの「チェック済みコレクションラッパー」はデバッグ時にとても役立ちます。文字列の組 s を考えてみます。一部のレガシーコードでなぜか整数を s に挿入しているとします。ラッパーなしでは、問題ある要素を文字列の組から読み取るまで問題に気が付かず、String への自動的に生成されたキャストは失敗します。この時点で問題の原因を突き止めるのは遅すぎます。しかし、次の宣言を考えてみます。

    Set<String> s = new HashSet<String>();
この宣言を次のように書き換えます。
    Set<String> s = Collections.checkedSet(new HashSet<String>(), String.class);
こうすると、レガシーコードが整数を挿入しようとした時点で、コレクションが ClassCastException をスローします。得られるスタックトレースにより、問題を診断し、修復することが可能です。

汎用型は、できるだけどんな場所でも使用することをお勧めします。コードを汎用化する労力はかかりますが、コードがわかりやすくなり、型の安全性が保証されます。汎用ライブラリを使用するのは簡単ですが、汎用ライブラリを記述したり、既存のライブラリを汎用化するには専門知識が必要です。注意点が 1 つあります。コンパイルしたコードを 5.0 より前の仮想マシンに配備する場合、汎用型やその他の Tiger の機能は使用しないでください。

C++ のテンプレート機構に詳しい場合は、汎用型がそれに類似していると思うでしょう。しかし、類似点は表面的なものです。汎用型では、特別な用途ごとに新規クラスを生成せず、「テンプレートのメタプログラミング」を許可していません。

汎用化についての詳細は、「Generics Tutorial」を参照してください。


Copyright © 2004 Sun Microsystems, Inc.All Rights Reserved.

コメントや提案をお寄せください。

Sun

Java ソフトウェア