ほかのAPIの場合は、プログラムと並行して保守される「付属ファイル」が必要となります。たとえばJavaBeansにはBeanと並行して保守されるBeanInfo
クラスが必要で、Enterprise JavaBeans (EJB)の場合は配備記述子が必要です。これらの付属ファイル内の情報がプログラム自体の注釈として保守されれば、扱いが簡単でエラーが発生しにくくなります。
Javaプラットフォームには、様々な臨時注釈メカニズムがあります。たとえばtransient
修飾子は、フィールドが直列化サブシステムによって無視されるべきであることを示す臨時注釈であり、@deprecated
javadocタグはそのメソッドを使用すべきでないことを示す臨時注釈です。このプラットフォームには汎用の注釈(メタデータとも呼ばれる)機能があります。この機能では、ユーザー独自の注釈型を定義して使用できます。この機能は、注釈型の宣言構文、宣言の注釈構文、注釈を読み取るAPI、注釈のクラス・ファイル表現およびjavac
ツールが提供する注釈処理サポートで構成されます。
注釈はプログラム・セマンティックスに直接影響しませんが、ツールやライブラリがプログラムを扱う方法に影響します。そのため、実行中のプログラムのセマンティックスに影響する場合があります。注釈はソース・ファイルから、クラス・ファイルから、または実行時にリフレクション・ベースで読み取ることができます。
注釈はjavadocタグを補完します。通常、マークアップの目的がドキュメントの変更または生成である場合はjavadocタグを使用し、そうでない場合は注釈を使用することをお薦めします。
通常のアプリケーション・プログラマは注釈型を定義する必要はまったくありませんが、定義することは難しくありません。注釈型の宣言は、通常のインタフェースの宣言に似ています。アットマーク(@
)がinterface
キーワードの先頭に付きます。それぞれのメソッド宣言が、注釈型の要素を定義します。メソッド宣言は、パラメータやthrows
節を持つことはできません。戻り値の型は、プリミティブ、String
、Class
、列挙、注釈、およびそれらの型の配列にかぎられます。メソッドはデフォルト値を持つことができます。注釈型の宣言の例を示します。
/** * Describes the Request-For-Enhancement(RFE) that led * to the presence of the annotated API element. */ public @interface RequestForEnhancement { int id(); String synopsis(); String engineer() default "[unassigned]"; String date() default "[unimplemented]"; }
注釈型を定義したら、宣言を注釈できます。注釈は特殊な修飾子で、ほかの修飾子(public
、static
、final
など)が使用できる場所であれば使用できます。規則により、注釈はほかの修飾子の前に置きます。注釈は、アットマーク(@
)に続く注釈型と、要素 - 値ペアをカッコで囲んだリストで構成されます。値はコンパイル時定数でなければなりません。上記で宣言した注釈型に対応する注釈を使用したメソッド宣言の例を示します。
@RequestForEnhancement( id = 2868724, synopsis = "Enable time-travel", engineer = "Mr. Peabody", date = "4/1/3007" ) public static void travelThroughTime(Date destination) { ... }
要素のない注釈型は、マーカー注釈型と呼ばれます。次に例を示します。
/** * Indicates that the specification of the annotated API element * is preliminary and subject to change. */ public @interface Preliminary { }
マーカー注釈では、次のようにカッコを省略できます。
@Preliminary public class TimeTravel { ... }
単一要素の注釈の場合、その要素は、次のようにvalue
という名前にしなければなりません。
/** * Associates a copyright notice with the annotated API element. */ public @interface Copyright { String value(); }
要素名がvalue
である単一要素注釈では、要素名と等号(=
)を省略できます。
@Copyright("2002 Yoyodyne Propulsion Systems") public class OscillationOverthruster { ... }
以上を組み合わせて、単純な注釈ベースのテスト・フレームワークを構築します。まず、メソッドがテスト用メソッドであり、テスト・ツールで実行しなければならないことを示すマーカー注釈型が必要です。
import java.lang.annotation.*; /** * Indicates that the annotated method is a test method. * This annotation should be used only on parameterless static methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { }
注釈型の宣言自体が注釈されています。このような注釈はメタ注釈と呼ばれます。1番目(@Retention(RetentionPolicy.RUNTIME)
)は、この型の注釈はVMによって保持されるため、実行時にリフレクション・ベースで読み取ることができる、ということを表しています。2番目(@Target(ElementType.METHOD)
)は、この注釈型はメソッド宣言だけを注釈することを表しています。
次のサンプル・プログラムでは、一部のメソッドが前述のインタフェースで注釈されています。
public class Foo { @Test public static void m1() { } public static void m2() { } @Test public static void m3() { throw new RuntimeException("Boom"); } public static void m4() { } @Test public static void m5() { } public static void m6() { } @Test public static void m7() { throw new RuntimeException("Crash"); } public static void m8() { } }
次の例はテスト・ツールです。
import java.lang.reflect.*; public class RunTests { public static void main(String[] args) throws Exception { int passed = 0, failed = 0; for (Method m : Class.forName(args[0]).getMethods()) { if (m.isAnnotationPresent(Test.class)) { try { m.invoke(null); passed++; } catch (Throwable ex) { System.out.printf("Test %s failed: %s %n", m, ex.getCause()); failed++; } } } System.out.printf("Passed: %d, Failed %d%n", passed, failed); } }
このツールは、クラス名をコマンド行引数として取り、指定されたクラスのすべてのメソッドを反復処理して、Test
注釈型(上記で定義)で注釈されている各メソッドを呼び出そうとします。メソッドがTest
注釈かどうかを調べるリフレクション照会の箇所を緑色で強調表示しています。テスト・メソッドの呼出しで例外がスローされる場合、テストは失敗したと見なされ、エラー・レポートが出力されます。最後に、テストの合格数と失敗数が示されたサマリーが出力されます。次の例は、前述のFoo
プログラムに対してテスト・ツールを実行したときの様子を示しています。
$ java RunTests Foo Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash Passed: 2, Failed 2このテスト・ツールは簡単なものですが、注釈の機能を例示しており、簡単に拡張して制限を解消できます。