Java

注釈

Language の目次


多くの API では、決まった型のコードがかなり必要になります。たとえば JAX-RPC Web サービスを記述するために、インタフェースと実装の組み合わせを提供する必要があります。この定形句は、リモートからアクセス可能なメソッドを示す注釈でプログラムが「装飾」されている場合に、ツールで自動的に生成することができます。

ほかの API の場合は、プログラムと並行して保守される「付属ファイル」が必要となります。たとえば JavaBeans には Bean と並行して保守される BeanInfo クラスが必要で、Enterprise JavaBeans (EJB) の場合は「配備記述子」が必要です。これらの付属ファイル内の情報がプログラム自体の注釈として保守されれば、扱いが簡単でエラーが発生しにくくなります。

Java プラットホームには、個々の目的に応じてさまざまな注釈の機構があります。たとえば transient 修飾子は、フィールドが直列化サブシステムによって無視されるべきであることを示す注釈であり、@deprecated タグは特定のメソッドを使用すべきでないことを示す注釈です。リリース 5.0 では、汎用の注釈 (「メタデータ」とも呼ばれる) 機能があります。この機能では、ユーザが独自の注釈型を定義して使用できます。この機能は、注釈型の宣言構文、宣言の注釈構文、注釈を読み取る API、注釈のクラスファイル表現、および注釈処理ツールで構成されます。

注釈はプログラムのセマンティクスに直接影響しませんが、ツールやライブラリがプログラムを扱う方法に影響します。そのため、実行中のプログラムのセマンティクスに影響する場合があります。注釈はソースファイル、クラスファイル、または実行時にリフレクションとして読み取ることができます。

通常のアプリケーションプログラマは注釈型を定義する必要はまったくありませんが、定義することは難しくありません。注釈型の宣言は、通常のインタフェースの宣言に似ています。単価記号 (@) が interface キーワードの先頭に付きます。それぞれのメソッド宣言では、注釈型の要素を定義します。メソッド宣言には、パラメータや throws 節を含めることはできません。戻り値の型は、プリミティブ、StringClass列挙、注釈、およびそれらの型の配列に限られます。メソッドには「デフォルト値」を設定できます。注釈型の宣言の例を示します。

/**
 * 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]"; 
}

注釈型を定義したら、宣言を注釈することができます。注釈は特殊な修飾子で、ほかの修飾子 (publicstaticfinal など) が使用できる場所であれば使用できます。ただし、注釈はほかの修飾子に先行するという規則があります。注釈は、単価記号 (@) に続く注釈型と、要素 - 値ペアを括弧で囲んだリストで構成されます。値はコンパイル時に定数でなければなりません。上記で宣言した注釈型に対応する注釈を使用したメソッド宣言の例を示します。

@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
このテストツールは簡単なものですが、注釈の機能を例示しており、簡単に拡張して制限を解消できます。
Copyright © 2004 Sun Microsystems, Inc.All Rights Reserved.

コメントや提案をお寄せください。

Sun

Java ソフトウェア