|
Metadata は魔法の言葉 |
||||
|
||||
Metadata ってよくわからない言葉ですね。データ自身の情報をあらわすための情報とでもいえばいいのでしょうか。 たとえば、JDBC の ResultSet にはメタデータを表すための ResultSetMetadata というクラスがあります。ResultSet クラスが実際にデータベースから取ってきた情報を保持しているのに対し、ResultSetMetadata クラスはカラムの名前や型など ResultSet クラスが保持する情報を説明するための情報を保持しています。 このように Metadata は情報自体を説明するための情報ということがいえます。情報の注釈といってもいいと思います。 それではなぜ今そんな情報をあつかえるようになったのでしょう。 プログラミングをしていると、プログラムロジックには関係のないコーディングというのが少なからず存在します。たとえば、RMI を使用するときに Remote インタフェースを派生させたインタフェースを書く必要があります。 同様に JAX-RPC も Web Services として公開するためには、Remote インタフェースを派生させたインタフェースを書く必要があります。Web Services の場合は WSDL も書かなくてはいけません。 しかし、このような作業は定型処理であり、できれば自動化したいのが人情です。 そこで、役立つのが Metadata です。Metadata を使用して Web Services として公開したいメソッドに注釈をつけておきます。そうすれば、たとえば JAX-RPC の wscompile などのようなツールが Metadata を読み込んで、自動でインタフェースや WSDL を作成することができるようになるかもしれないのです。 実をいうと、Tiger だけでは Metadata の利点はあまりありません。Metadata を扱うことのできるツールやライブラリが充実してこないことには Metadata のおいしさが味わえないのです。 しかし、J2EE 1.5 では多くのライブラリやツールが Metadata をサポートすることが発表されています。一例をあげると
J2EE 1.5 が登場するのはずいぶん先になると思いますが、今のうちに Metadata になじんでおけば J2EE 1.5 も怖くありませんね。 Metadata は他の Tiger の新機能と同様に JCP で仕様策定されています。JSR-175 になります。仕様などは そこで見ることができます。逆に J2SDK beta のドキュメントには Metadata についての記述はほとんどないので (もちろん JavaDoc はありますが)、JSR-175 を参照する必要があります。 それでは、さっそく Metadata に触れてみましょう。
|
|
||||||||||||||||
Metadata を記述するにはアノテーションを使用します。習うより慣れろということで、次のソースをご覧ください。
かなり作為的なソースですがそれはおいておいて、@Log というのが Metadata というかアノテーションになります。
そして、この AnnotationSample クラスを使用するのが、AnnotationSmapleClient クラスです。
これをコンパイル、実行してみましょう。javac を実行している行は 2 行にわたっていますが、実際には 1 行です。
別段何ということもありません。 さて、ここで次に示すツールを使用してみましょう。
そして、この JAR ファイルを使用して JavaDoc を実行します。環境変数 JAVA_HOME は J2SDK をインストールしたディレクトリを示しています。
すると、AnnotationSampleProxy.java と AnnotationSampleFactory.java というファイルが生成されました。 先ほどの AnnotationSampleClient クラスを生成されたクラスを使用して書き換えましょう。AnnotationSampleFactory クラスには AnnotationSample インスタンスを生成する create というメソッドが定義されているので、これを使用してオブジェクトを生成するようにしました。
コンパイルして、実行です。 あ、忘れていました。実行する前にちょっと細工がいります。%JAVA_HOME%\jre\lib\logging.properties ファイルを 2 箇所修正しておきます。
今度こそ準備ができたので、実行です。
AnnotationSample クラスには一切手を入れていないのに、先ほどとはずいぶん違う出力がされました。この出力は Logging API を使用したログになります。この例ではコンソールにログを出力しましたが、もちろんファイルに出力することもできます。 どうしてこんなことができるのでしょうか。 それは Metadata を使用したからです。 JavaDoc を実行したときに 2 つのファイルが生成されましたが、それを生成しているのが LogLet という Doclet です。Doclet というのは JavaDoc をカスタマイズするために使用されるもので、通常 API マニュアルを生成するのも Doclet で実装されています。 Doclet には JavaDoc からクラスの詳細情報が渡されます。その中にはアノテーションの情報も含まれています。LogLet はそれを見て @Log と書かれているメソッドはログを出力するような AnnotationSample クラスのプロキシクラスを生成します。また、プロキシがすぐに使えるようなファクトリを生成しているのです。 生成されたプロキシクラスはつぎのようになっています (見やすいように整形をしています)。
AnnotationSample#bar() メソッドは @Log がついていないので、このプロキシクラスには表れていません。その他のメソッドだけがログ出力されるわけです。 このようなクラスを生成できるのも Metadata が使えるからなのです。
|
|
||||||||||||||||||||||
アノテーションを記述することができるのはクラス、インタフェース、アノテーション、メソッド、フィールド、そしてなんとパッケージにも記述することができます。いずれの場合もアノテーションはモディファイアと同列に扱われます。ようするに public や static, final などと同列だということです。 ですから、アノテーションをクラスやメソッドなどに付記するときには public などと一緒に記述することになります。 たとえば、クラスにアノテーションをつけるには、次の 1. と 2. の方法のどちらでも OK ということです。
また、アノテーションもパッケージがあるので、適時 import 文で指定する必要があります。 アノテーション は次の 3 種類に分けることができます。
これらの違いは属性 (アノテーションの言葉はメンバとなっているので、以後はメンバと表記します) があるかないかということです。 たとえば、先の LogLet で使用した @Log というアノテーションは value というメンバを持っています。このメンバはログのレベルを表すものです。次のように記述した場合はログのレベルが SEVER であるということを示しています。
先ほどの 3 種類は、マーカ アノテーションがメンバを持たないもの、シングルメンバ アノテーションが value というメンバだけを持つもの、それ以外という区分になります。 Clonable や Serializable などメソッドを定義していないインタフェースをマーカ インタフェースといいますが、マーカ アノテーションもそれと同様です。アノテーションがあるという情報だけを付記できるわけです。 シングルメンバ アノテーションはメンバが value だけなので、次に示したように省略した書き方をすることができます。
メンバで使用できる型には
@Log アノテーションでは LogLevel という enum 型を定義して使用しています。配列の場合は {} を使用し、カンマ区切りで記述します。
もちろん、複数の属性を持っているアノテーションもあります。
シングルメンバ アノテーションの value メンバが配列の場合、配列の要素が 1 つであれば次のような記述も可能です。
アノテーションがどのような型のどのようなメンバを持つかは JavaDoc に記載されています。たとえば、java.lang.annotation.Target の JavaDoc は次のようになっています。
valuepublic abstract ElementType[] value これを見ると enum の java.lang.annotation.ElementType 型の配列である value という属性があることがわかります。したがって、このアノテーションはシングルメンバ アノテーションだということです。 ところで、この Target というアノテーションは自分自身を自分で注釈しています。なんかへんな感じですが、こういうことも可能だということです。 Annotation を使うのはそれほど難しくないのですが、Annotation がどのような意図を持って定義されているかを理解する必要があります。どのようなアノテーションがあって、どのような時に使用するかを覚えなくては、有効にアノテーションを使えないというわけです。
|
|
||||||||||
普通のプログラミングでは Metadata を使うことはあっても、それを定義したり、Metadata を扱うツールなどを作ることはめったにないと思います。 しかし、あえてアノテーションを定義してみましょう。定義自体はとても簡単です。 アノテーションは一種のインタフェースとして定義されます。interface と記述するところを @interface と書きます。ただし、通常のインタフェースに比べると以下のような制約があります。
これを守れば、定義自体はインタフェースとほとんど同じです。もっとも単純なマーカ アノテーションだと次のようになります。
アノテーションを書くときにメンバの値を指定できましたが、メンバはなんとメソッドとして定義します。たとえば、@Log は次のようになっています。
@Log のメンバは value でしたが、それがそのままメソッドになってしまいます。 メソッドとして定義はしますが、いろいろと制約の多いメソッドになります。
@Log の場合は戻り値が LogLevel 型ですが、これは enum なので使用できるわけです。ところで、この戻り値の型はどこかで見たことありませんか? そうです、メンバとして使用できる型と一緒です。 要するに、メンバに値を代入することは、このメソッドをコールしたときに代入された値が戻るようになっているのです。値を読み出すのはもうちょっと後で試してみましょう。 戻り値にアノテーションが使えるからといって、自分をメソッドの戻り値にすることはできません。ようするに次の例はだめということです。
これに関連して循環するような定義もできません。
もう 1 つ、通常のメソッドと違うのがデフォルト値を設定できるということです。デフォルト値はメソッドの定義の後に default と記述し、その後に記述します。 たとえば、@Log は実をいうとデフォルト値が設定されていました。
いままではメソッドは 1 つでしたが、もちろん複数書くことも可能です。
定義も簡単でしょ ;-) 最後に @Log アノテーションの全体を示しておきます。
value メソッドで使用する LogLevel クラス (というか enum 型ですが) は次のようになっています。
|
|
||||||||||||||||||
アノテーションの定義までできるようになりましたが、定義してもそれを読み込めるようにならなければ利用することはできません。 アノテーションの読み込みには 2 つの方法があります。
Reflection を使用する場合、アノテーションが記述されたクラスをロードし、リフレクションを利用してアノテーションを読み込みます。 オフラインで読み込むときでもリフレクションを使用してもいいのですが、そのためにはクラスをロードしなくてはいけません。クラスをロードせずに読み込む方法として LogLet でも使用した JavaDoc を利用する方法と apt というツールが提供されています。apt は別のドキュメントに解説しました。 まずはリフレクションを使った方法から試してみましょう。 リフレクションでアノテーションを扱うために AnnotatedElement というインタフェースが導入されています。AnnotatedElement インタフェースで定義しているメソッドは次の 4 種類です。
AnnotatedElement インタフェースをインプリメントしているクラスには
です。Class クラスだけはこれ以外に isAnnotation というメソッドがあって、Class クラスが指しているクラスがアノテーションのクラスかどうかを判別することができます。 それでは手始めにどのようなアノテーションが記述されているか調べて見るプログラムを作ってみましょう。
調べる順序として次のようにしたいと思います。
まずはクラスがアノテーションかどうかです。これには前述した Class#isAnnotation メソッドを使用します。
調べるクラスの Class クラスがないと話にならないので、まず Class#forName メソッドを使用して Class オブジェクトを取得します。 次に Class#isAnnotation メソッドを使用してアノテーションかどうかを調べています。 次の行からがクラス、フィールド、コンストラクタ、メソッドと順に調べていくためのコードになっています。readAnnotation メソッドは次のようになっています。
フィールドもコンストラクタもメソッドも AnnotatedElement インタフェースをインプリメントしているので、こんな書き方をすることができます。 そして、拡張 for 文を使用して要素を切り出し、readAnnotations メソッドをコールしています。肝心の readAnnotations メソッドは次のようになります。
まず、AnnotatedElement#getAnnotaions メソッドを使用して記述されているすべてのアノテーションを取得します。アノテーションが記述されていない場合、AnnotatedElement#getAnnotations メソッドの戻り値は空の配列が戻ってくるので、それを調べるためのメソッドが次の if 文になっています。 アノテーションがあればそれを 1 つずつ切り出し、出力しています。 それではコンパイル、実行してみます。実行するときには引数としてクラスを指定します。まずは標準で提供されている java.lang.annotation.Target クラスを読み出してみましょう。
Target クラスはアノテーションで、クラスには Retention アノテーションと Target アノテーションが付記されています。また、value メソッドはアノテーションがないことが出力から分かります。 次にサンプルとして作った AnnotationSample クラスを読み込ませてみます。
あれっ ? ちゃんとアノテーションが出力されていません。これはどういうことなのでしょうか。 実をいうとこれでかなり悩みました。どうして出力されないのか ? 答えはクラスファイルにありました。 アノテーションを導入するために、当然ながらクラスファイルのフォーマットが変更されています。クラスファイルのヘッダ部分にアノテーションの情報が埋め込まれるようになっています。単にアノテーションの情報だけかと思っていたのですが、どうやら違うようです。 AnnotationSample クラスのクラスファイルをダンプしてみると、次のようになりました。
注目していただきたいのは、赤い字のところです。RuntimeInvisibleAnnotations と書かれています。同じように Target クラスをダンプしてみると、
こちらは RuntimeVisibleAnnotations と記述されています。 どうやらこの違いがリフレクションでアノテーションを読めるかどうかの違いのようです。そこで、Target アノテーションと Log アノテーションを比較してみました。主だった違いは次の 2 つです。
Target アノテーションは次のように定義されています (コメントは省略しています)。
標準で用意されているこれらのアノテーションは後で説明するとして、一番怪しいのは Retention アノテーションです。value メンバには enum の RetentionPolicy 型を指定できるのですが、RetentionPolicy 型が定義しているのは
の 3 つです。それぞれの JavaDoc の説明を見てみると、アノテーションがクラスファイルに含むようにするには CLASS か RUNTIME を使用するように書かれています。SOURCE の場合はクラスファイルにアノテーションの情報は記述されないようです。 CLASS と RUNTIME の違いは、実行時にアノテーションを読めるかどうかで、RUNTIME の場合のみ読み込めると説明されています。 そこで、Log アノテーションにこれを付加してみました。
さて、結果は...
見事読み出すことができました。また、AnnotationSample.class をダンプしても RuntimeVisibleAnnotations と記述されているのが確認できます。 ところでこの RuntimeVisibleAnnotations とか RuntimeInvisibleAnnotations というのは何なんでしょう。何のことはなく、メタデータの仕様書に書いてありました。って、はじめから仕様書読んでればこんな苦労はしなかったのに ... クラスファイルにはアノテーションのアトリビュートとして次の 5 種類のアトリビュートが定義されています。
これらのアトリビュートはそれぞれ情報を保持しているのですがそこまで見る必要はなさそうです。問題はこららのアトリビュートが何を表しているかということです。 RuntimeVisibleAnnotations は今まで見てきたように実行時にいつでもアノテーションを参照することができるためのアトリビュートです。 逆に RuntimeInvisibleAnnotations は実行時にリフレクションでアノテーションを参照することはできません。しかし、アノテーションの情報はクラスファイルに記述されるので、リフレクション以外の別の機構を使えば読み出すことは可能なはずです。 RuntimeVisibleParameterAnnotations と RuntimeInvisibleParameterAnnotations は RuntimeVisibleAnnotations/RuntimeInvisibleAnnotations とほとんど同じですが、メソッド定義の中だけに記述されるものらしいです。 最後の AnnotationDefault はアノテーションのメンバのデフォルト値に関するアトリビュートを示しているようです。 これらを明確に意識する必要はないと思いますが、Retention アノテーションによってこれらのアトリビュートが変化して、リフレクションで読めるかどうかを決めるということは意識したほうがいいと思います。 次にもう少しアノテーションを詳しく見てみるサンプルを作ってみました。Log アノテーションを読み出すサンプルです。
アノテーションを取得するには AnnotatedElement インタフェースの getAnnotation(Class<T> annotationType) メソッドを使用します。戻り値はパラメータ化されているので、直接引数で指定したアノテーション型に代入することが可能です。 基本的なところは先ほどの AnnotationsReader クラスと同じなので、実際に getAnnotation メソッドを使用している部分を次に示します。
getAnnotations メソッドを使用することで Log オブジェクトを取得することができました。Log アノテーションが記述されていない場合、戻り値は null になります。 Log オブジェクトが取得できたので、メンバの値を取得することができます。アノテーションの定義でメンバはメソッドとして定義すると書きましたが、ここでそれを利用するわけです。 Log アノテーションのメンバ value は value() メソッドに対応しています。value() メソッドをコールすると、アノテーションで記述された値を取得することができるのです。 このサンプルを実行してみると、次のようになりました。
メンバの値も正しく読み込まれているのがわかると思います。
|
|
||||||||||||||||
これまでは実行時にアノテーションを読み出すことを試してみましたが、それ以外の方法でもアノテーションを読み出すことが可能です。 そのために提供されているのが Doclet API です。Doclet は JavaDoc から起動されます。通常は API ドキュメントを記述するためですが、JavaDoc から得ることができるクラスやメソッドの情報を使って他の用途に使用することもできます。 また、Doclet を使うと、リフレクションでアノテーションを読み出すのとは異なり、クラスファイルが必要ありません。つまり、クラスローダリングしなくてもアノテーションを読み込むことが可能です。 それでは、一番はじめに使用した LogLet を材料に、Docllet でのアノテーションの扱いについて解説していきましょう。
Doclet は Applet や Servlet と違い、クラスではありません。Doclet を使用するには static な start メソッドを定義するだけです。
引数の RootDoc オブジェクトにクラスやメソッド、アノテーションの情報が保持されています。戻り値は boolean で解析が成功すれば true、失敗したら false を返すようにします。 RootDoc クラスのようなクラス JavaDoc のページの Doclet API で JavaDoc を読むことができます。Doclet についての詳細は省略するとして、アノテーション関連の Doclet のクラスは
の 5 つです。 AnnotationTypeDoc クラスはアノテーション型自体を表しているクラスです。AnnotationTypeMemberDoc クラスがそのアノテーションのメンバの情報を保持しています。 残りの 3 つがソース中に記述されたアノテーションの情報を表すためのクラスです。AnnotationTypeDoc クラスが記述されたアノテーションを表し、そのメンバの情報が AnnotationTypeMemberDoc に保持されます。実際のメンバの値は AnnotationValue クラスで表されます。 実際に記述されたアノテーションを取得するには ProgramElementDoc インタフェースの annotations メソッドを使用します。annotations メソッドの戻り値は AnnotationDesc クラスの配列なので、後はそこから必要な情報を引き出していきます。 たとえば、Log アノテーションがあるかどうかをチェックする部分は次のようになりました。
引数の members は ProgramElementDoc インタフェースの配列ですが、実際にはそれをインプリメントしたコンストラクタ (ConstructorDoc クラス) もしくはメソッド (MethodDoc クラス) の情報が入っています。 3 行目で annotations メソッドを使用して AnnotationsDesc クラスの配列を取り出し、そこから AnnotationTypeDoc オブジェクトを取得します。そして、その名前が "Log" であるかどうかをチェックしています。 Doclet ではこのようにアノテーションを扱います。 せっかくなので、作成した Loglet の解説もしましょう。 Loglet クラスはアノテーションを利用してログを書き出すプロキシとそのプロキシを生成するためのファクトリクラスを自動生成しています。全体的な流れは以下に示すアクティビティ図は図 1 のようになります。アクティビティ図は UML で定義されている図の中の 1 つで、フローチャートのような使い方をします。
図 2 はプロキシやファクトリを生成するときに、それぞれのメソッド・コンストラクタを作成するときのフローになります。この流れに基づいて説明をしていきたいと思います。 図 1 ではループになっていませんが、RootDoc クラスには複数のクラスの情報が入っているので、すべてのクラスについて図 1 のフローを行います。
まずはプロキシ生成から見ていきます。プロキシはクラス定義などの部分、コンストラクタ、メソッド、クラスの最後の部分という順番で作成していきます。次に示したようにプロキシのファイル名はオリジナルのクラス名 + Proxy.java というようにしています。
writeHeader メソッドや writeFooter メソッドはアノテーションには関係ないので、省略します。writeConstructor メソッドと witeMethod メソッドが図 2 のフローで処理を行っています。それぞれ似かよった処理になるので、ここでは writeMethod メソッドだけを見ていきましょう。 writeMethod メソッドはかなり長いので行番号を振ってみました。
まずは図 2 にあるとおり、public もしくは protected であるかのチェックです (2 行目から 4 行目)。public/protected であったとしても、final なメソッドの場合はオーバライドしないようにしています。 6 行目でこのメソッドに付加されているアノテーションを取得します。アノテーションがない場合、annotations メソッドは空の配列を返すので、7, 8 行目でそれをチェックし、アノテーションがなければ writeMethod メソッドを抜けます。 11 行目からが Log アノテーションの内容を調べている部分です。annotations メソッドで取り出した AnnotationDesc オブジェクトから、そのアノテーションの型情報を保持している AnnotationTypeDoc オブジェクトを取得します (14 行目)。 それが Log であるかどうかを調べているのが 15 行目です。名前を得るために com.sun.javadoc.Doc インタフェースで定義されている name メソッドを使用します。name メソッドはパッケージを含まない名前を返すので、それと "Log" を比較しています。 アノテーションが Log であれば、value メンバの値を取得します。18 行目の extractLogLevel メソッドは次のようになっています。
メンバの情報を取得するには AnnotationDesc クラスの elementValues メソッドを使用します。AnnotationDesc.ElementValuePair クラスはメンバの型情報を持つ AnnotationTypeMemberDoc オブジェクトとメンバの値を保持する AnnotationValue オブジェクトを保持しています。 メンバの値をとるためには value メソッドを使用します。Log の場合はそれがログのレベルとなっています。 気をつけなくてはいけないのが、AnnotationDesc#elementValues メソッドはメンバが明記されているものだけを返します。デフォルト値が設定されているものは、このメソッドでは得られる配列には含まれていません。 デフォルト値は AnnotationTypeMemberDoc クラスが保持しています。 ログのレベルが分かったので、後はプロキシーのコードを出力するだけです。基本的には次のようなコードを出力することになります。
戻り値がない場合はじゃっかん異なりますが、だいたい同じと考えていいと思います。たとえば、メソッドのシグネチャを書く部分は 30 行目から 36 行目になります。 まず、parameters メソッドで引数の情報を取得します。そして、記述するメソッドのモディファイア (modifiers メソッド)、戻り値 (returnType メソッド)、メソッド名 (name メソッド) をまず記述します。 戻り値の型を出力しているところで name メソッドでなく qualifiedTypeName メソッドを使用しているのは、パッケージからフルに書かせるためです。パッケージを書かないと import 文を記述しなくてはいけないのですが、それよりは qualifiedTypeName メソッドを使用したほうが単純にすみます。 メソッドの引数の並びは writeParameterDefs メソッドで記述しています。
ループが param.length - 1 までになっているのは、最後の引数だけは "," (カンマ) が書かれないためです。 後は同じようにして、ログを書き、親クラスを呼び出し、ログを書き、return を書いて、最後のカッコを書くという流れになります。 このようにしてアノテーションを使うことで、本来プログラムには関係ない情報を付加することができます。そして、その情報を使うことでファイルの自動生成などができるのです。
|
|
||||||||||||||||||||||||||||||
Tiger では標準でいくつかのアノテーションが定義されています。前述した Target アノテーションや Retention アノテーションも標準で定義されているアノテーションです。これらの使い方を調べてみましょう。 java.lang.annotation.Target Target アノテーションはアノテーションを修飾するためのアノテーションです。ドラフトにはアノテーションを細くするためのアノテーションなのでメタアノテーションと記述されています。 Target はアノテーションの使用法について説明を加えるものです。すなわち、あるアノテーションがクラスに付記されるものなのか、メソッドに付記されるものなのか、それともフィールドに付記されるものなのかという情報を記述します。 Target アノテーションの value メンバは ElementType の配列となります。ElementType は次のように定義されています。
たとえば、A というアノテーションがクラスに付記される場合、次のように定義します。
また、Log アノテーションであればコンストラクタとメソッドに対応しているので、次のように書くことができます。
Target アノテーションの value メンバは配列なので、こういう複数の要素に使用されるアノテーションも定義することができます。 Target アノテーションは javac が使用しています。たとえば、Log アノテーションを次のようにクラスに付加してみます。
これをコンパイルすると
というように怒られてしまいます。
java.lang.annotation.Retention Retention アノテーションは前述したようにアノテーションの用途を説明するためのメタアノテーションです。メンバは value だけで型は RetentionPolicy です。先に示したように RetentionPolicy は 3 つの定数を定義しています。
このアノテーションも Target アノテーションと同様に javac か解釈します。
java.lang.annotation.Documented Documented アノテーションはメンバを持たないマーカアノテーションです。やはり、アノテーションを修飾するためのメタアノテーションとなります。 Documented アノテーションはアノテーションを JavaDoc などで扱うかどうかを示しています。 たとえば、Log アノテーションは Documented アノテーションを使用していないので、AnnotationSample クラスの JavaDoc を作成するとつぎのようになります (部分)。
Log アノテーションに Documented アノテーションを付加すると次のように変化しました。
コンストラクタの説明のところに @Log と記述されているのが分かると思います。 このように Documented アノテーションは JavaDoc が解釈して、API ドキュメントに情報を反映させるものなのです。
java.lang.annotation.Inherited Inherited アノテーションもメンバを持たないメタ マーカアノテーションです。 このアノテーションが付記されたアノテーションを使用すると、使用されたクラスの派生クラスにもそのアノテーションが引き継がれます。たとえば、次のようなアノテーションを考えて見ます。
これを使用するクラスとして
これで前に作った AnnotationsReader クラスでアノテーションを読み取ってみましょう。
これだと InheritedAnnotationsSample2 クラスにはアノテーションはついていません。 そこで InheritedAnnotation アノテーションに次のように Inherited アノテーションをつけてみます。
同様に AnnotationsReader を実行してみると
InheritedAnnotationSample2 クラスは変更していないのに、InheritedAnnotation アノテーションが付記されているという結果になりました。 このように Inherited アノテーションを使用することで、アノテーションの影響する範囲を自身のクラスだけにするか派生クラスを含めたものにするかのコントロールをすることができます。
java.lang.Override java.lang パッケージの 2 つのアノテーションは java.lang.annotation パッケージで定義されたアノテーションと異なり、メタアノテーションではありません。両方とも普通に使用されるアノテーションで、Override はメソッドに付記することができます。 Override アノテーションは派生クラスが親クラスのメソッドをオーバライドしたときにつけるようにします。Override アノテーションがあると、javac は親クラスのメソッドを参照して正しくオーバライドしているかチェックします。 たとえば、つぎのように親クラスのメソッドを 1 つはオーバライド、1 つはオーバロードしたとします。両方に Override アノテーションをつけておくと、javac がエラーを出力します。
もとのソースの 7 行目はオーバロードしたほうなので、正しくオーバライドしていないということを javac が検出します。 JavaOne で恒例となっている Joshua Bloch と Neal Gafter の Programming Puzzler でこんな問題が出されたことがあります。下のプログラムを実行すると出力はどうなるかという問題です。
答えは 3 択です。
さて、答えはどれでしょうか。 答えは 2. の false です。 この問題の肝は Object#equals の引数が Object クラスであるということにあります。オーバライドしたつもりでも、オーバロードになってしまったんですね。 こんな間違いを減らすために Overrides アノテーションは威力を発揮しそうです。
java.lang.Deprecated Deprecated アノテーションはメソッドなどが Deprecated であることを示すために使用されます。これまでは Depricated であることを示すには JavaDoc の @deprecated タグを使用していましたが、Deprecated アノテーションの方が簡潔に表すことができます。 たとえば、次のような例を見てみましょう。
これをコンパイルすると
そこで、-Xlint:deprecation を使ってコンパイルしなおすと次のように出力されます。
このようにメソッドを使用したりオーバライドすることを警告してくれます。
|
|
||||
虎の穴では恒例の裏側がどうなっているか調べて見ましょう。 まずはアノテーションの定義についてです。アノテーションは java.lang.annotation.Annotation インタフェースの派生インタフェースになるとドラフトに書いてあるのでそのとおりなのでしょう。 いつものように Jad を使って Log アノテーションを逆コンパイルしてみました。
やはり Annotation インタフェースの派生クラスになっています。しかし、Log アノテーションに付記していた Target などのアノテーションの情報はなくなってしまっています。 これはまだ jad が Tiger のクラスファイルに対応していないのでしかたがないでしょう。jad 1.5.6 でサポートしているクラスファイルのバージョンは 45.3, 46.0, 47.0 で、Tiger のクラスファイルのバージョンは 49.0 になります。 jad が Tiger のクラスファイルに対応したらもう少し詳しく見てみることにして、今はこのぐらいにしておきましょう。 ドラフトによれば、先に示したような RuntimeVisibleAnnotations などのアトリビュートを使用してアノテーションの情報が記述され、method_info 構造体の中に含まれるようになると書いてあります。 とりあえず、Annotation インタフェースがどのようなインタフェースかぐらいは見ておきましょう。Annotation インタフェースで定義されているメソッドは 2 つです。
これだけだとなんか拍子抜けしてしまいますね ^^;; メンバが配列の場合、メンバの要素すべてを比較しないと equlas メソッドが実装できません。そこで、java.util.Arrays クラスに hashCode(int[] a) などのプリミティブ型に対するメソッド群が用意されました。また、文字列を生成するときも、配列の要素すべてを記述しなくてはならないので、同様に Arrays クラスに toString(int[] a) などのメソッド群が用意されています。 オブジェクトの配列の場合は Arrays#deepHashCode(Object[] a)、Arrays#deepEquals(Object[] a1, Object[] a2)、Arrays#deepToString() が提供されます。
|
|
||||
この解説ではアノテーションを自分で定義して、それに対応したツールを作るところまで行いましたが、普通はそんなことしないとは思います。 しかし、だからといって Annotation が役に立たないわけではありません。 さまざまなツールやライブラリがアノテーションに対応したらこれほど便利なものはありません。重要なのはどのようなアノテーションがあって、それがどのような使われ方をするかを覚えなくてはいけないということです。 大変なような気がするかもしれませんが、普通にライブラリのクラスを覚えて使うのとたいした違いはありません。ただ、その使われ方が今までのクラスとはちょっと違うというだけです。 きたるべき J2EE 1.5 に備えて、今から使い方だけでも慣れておくといいのではないでしょうか。
今回使用したサンプルはここからダウンロードできます。 参考
(Mar. 2004) |
|