ジェネリックス


要素を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, 2020, Oracle and/or its affiliates. All rights reserved.