ほかの API の場合は、プログラムと並行して保守される「付属ファイル」が必要となります。たとえば JavaBeans には Bean と並行して保守される BeanInfo
クラスが必要で、Enterprise JavaBeans (EJB) の場合は配備記述子が必要です。これらの付属ファイル内の情報がプログラム自体の注釈として保守されれば、扱いが簡単でエラーが発生しにくくなります。
Java プラットフォームには、さまざまな臨時注釈メカニズムがあります。たとえば transient
修飾子は、フィールドが直列化サブシステムによって無視されるべきであることを示す臨時注釈であり、@deprecated
javadoc タグはそのメソッドを使用すべきでないことを示す臨時注釈です。リリース 5.0 では、汎用の注釈 (メタデータとも呼ばれる) 機能があります。この機能では、独自の注釈型を定義して使用できます。この機能は、注釈型の宣言構文、宣言の注釈構文、注釈を読み取る API、注釈のクラスファイル表現、および注釈処理ツールで構成されます。
注釈はプログラムセマンティクスに直接影響しませんが、ツールやライブラリがプログラムを扱う方法に影響します。そのため、実行中のプログラムのセマンティクスに影響する場合があります。注釈はソースファイルから、クラスファイルから、または実行時にリフレクションベースで読み取ることができます。
注釈は 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このテストツールは簡単なものですが、注釈の機能を例示しており、簡単に拡張して制限を解消できます。