ジェネリクス


要素を 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 がスローされることは絶対にありません。ジェネリクスを使用することにより、特に大規模なプログラムでは、可読性および堅牢性が向上するという実質的な効果があります。

ジェネリクス仕様リードの Gilad Bracha の言葉を言い換えると、型 Collection<String>c を宣言すると、変数 c は使用する際にいつでもどこでも正しい値を保持し、それをコンパイラが保証する (プログラムは警告なしで コンパイルされると仮定して) ということを意味します。一方、キャストの場合は、コードのある箇所でプログラマが正しいと考えた値ということになります。そして仮想マシンが、実行時になってプログラマが正しいかどうかを確認します。

ジェネリクスの主な用途はコレクションですが、ほかにも多くの使い方があります。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++ のテンプレートメカニズムに詳しい場合は、ジェネリクスがそれに類似していると思うでしょう。しかし、類似点は表面的なものです。ジェネリクスでは、専用化ごとに新規クラスを生成せず、「テンプレートメタプログラミング」を許可していません。

ジェネリクスについては、ほかにも多くの学ぶべき情報があります。「The Java Tutorial」の「Generics」レッスンを参照してください。


Copyright © 1993, 2013, Oracle and/or its affiliates. All rights reserved.