総称 |
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 c
の Collection
」と読みます。 総称を使用したコードの方がわかりやすく、安全です。安全でないキャストや、不要な括弧は取り除かれました。より重要な点として、メソッドの仕様記述をコメント部から署名部に移動しました。 そのため、型の制約が実行時に侵害されないことを、コンパイル時に検証できます。プログラムは警告なしでコンパイルされるため、実行時に ClassCastException
がスローされることは絶対にありません。総称を使用することにより、特に大規模なプログラムでは、可読性および堅牢性が向上するという実質的な効果があります。
総称仕様リードの Gilad Bracha の言葉を言い換えると、型 Collection<String>
の c
を宣言すると、変数 c
は使用する際にいつでもどこでも正しい値を保持し、それをコンパイラが保証する (プログラムは警告なしで コンパイルされると仮定して) ということを意味します。一方、キャストの場合 は、コードのある箇所でプログラマが正しいと考えた値ということになります。 そして仮想マシンが、実行時になってプログラマが正しいかどうかを確認します。
総称の主な用途はコレクションですが、ほかにも多くの使い方があります。 総称は「型消去」によって実装されます。総称情報は、コンパイル時にしか存在せず、コンパイル後はコンパイラによって「消去」されます。このアプローチの主な利点としては、総称コードと、パラメータ化されていない型 (技術的には「raw」型と呼ばれる) を使用するレガシーコードとの間に、総合的な相互運用性が実現する点です。主な欠点としては、パラメータの型情報を実行時に利用できない点と、動作が適切でないレガシーコードと相互運用すると、自動的に生成されたキャストが失敗するおそれがある点です。しかし、動作が適切でないレガシーコードと相互運用するときも、総称コレクションに対して実行時の型の安全性が保証されます。
総称は、できるだけどんな場所でも使用することをお勧めします。コードを汎用化する労力はかかりますが、コードがわかりやすくなり、型の安全性が保証されます。総称ライブラリを使用するのは簡単ですが、総称ライブラリを記述したり、既存のライブラリを汎用化するには専門知識が必要です。注意点が 1 つあります。コンパイルしたコードを 5.0 より前の仮想マシンに配備する場合、総称やその他の Tiger の機能は使用しないでください。
C++ のテンプレート機構に詳しい場合は、総称がそれに類似していると思うでしょう。 しかし、類似点は表面的なものです。総称では、特別な用途ごとに新規クラスを生成せず、「テンプレートのメタプログラミング」を許可していません。
総称化についての詳細は、「Generics Tutorial」を参照してください。
WeakReference
、ThreadLocal
などのホルダークラスはすべて汎用化され、総称を利用できるように改良されました。クラス Class
も汎用化されています。Class リテラルは「型トークン」として機能するようになったため、実行時とコンパイル時両方の型情報を提供できます。これにより、新しい AnnotatedElement
インタフェースの getAnnotation
メソッドのように、静的ファクトリの形式を利用できるようになります。
<T extends Annotation> T getAnnotation(Class<T> annotationType);
これは総称メソッドです。型パラメータ T の値を引数から推測して、次の例のように T
の適切なインスタンスを返します。
Author a = Othello.class.getAnnotation(Author.class);
総称の前に、結果を Author
にキャストする必要があります。また、Annotation
のサブクラスを表す実際のパラメータをコンパイラがチェックする方法はありません。
java.util.Collections
クラスは、実行時の型の安全性を保証するラッパークラスに装備されました。同期化され変更不可能なラッパーと似た構造を持ちます。これらの「チェック済みコレクションラッパー」はデバッグ時にとても役立ちます。文字列の組 s
を考えてみます。 一部のレガシーコードでなぜか整数を s
に挿入しているとします。ラッパーなしでは、問題ある要素を文字列の組から読み取るまで問題に気が付かず、String
への自動的に生成されたキャストは失敗します。この時点で問題の原因を突き止めるのは遅すぎます。しかし、次の宣言を考えてみます。
Set<String> s = new HashSet<String>();
この宣言を次のように書き換えます。
Set<String> s = Collections.checkedSet(new HashSet<String>(), String.class);
こうすると、レガシーコードが整数を挿入しようとした時点で、コレクションが ClassCastException
をスローします。得られるスタックトレースにより、問題を診断し、修復することが可能です。
Copyright © 2004 Sun Microsystems, Inc. All Rights Reserved.
Java Software