kagamihogeの日記

kagamihogeの日記です。

The Java TutorialsのType Annotationsのところをテキトーに訳した

The Java TutorialsType Annotations and Pluggable Type Systemsのセクションを読んで訳した。

Type Annotations and Pluggable Type Systems

Java SE 8 release以前では、アノテーションは宣言にのみ付与可能でした。Java SE 8 release以降では、アノテーションは型が使用される場所ならどこでも(any type use)付与可能です。この意味するところは、アノテーションは型を使う場所でならどこででも使用可能、ということです。型が使用される場所の例としては、new式でのインスタンス生成・キャスト・implements節・throws節、などです。アノテーションのこの形式はtype annotationと呼ばれ、いくつかの例をAnnotations Basicsに載せています。

Type annotationsは強力な型チェックを保証するJavaプログラムの解析方法を改良するために開発されました。Java SE 8 releaseは型チェックフレームワークを提供しませんが、型チェックフレームワークを開発(もしくはダウンロード)することが出来ます。型チェックフレームワークとは、Javaコンパイラと共に使用する一つ以上のpluggable modulesとして実装されるものです。

たとえば、プログラム内の個々の変数にnullを決して代入させないようにしたい、とします。つまり、NullPointerExceptionの発生を避けたい、という意味です。この目的をチェックするためのカスタムプラグインを作ることができます。個々の変数に決してnullが代入されないことを指示するアノテーションを付与するコードを、既存のコードに追加します。変数宣言は以下のようになります。

@NonNull String str;

このコードをコマンドラインNonNullを含めてコンパイルするとき、コンパイラは潜在的な問題を発見した場合にはwarningを表示します。エラーを回避するにはその部分のコードを修正します。すべてのwarningを取り除けば、個々のエラーはプログラム実行時には発生しないことになります。

それぞれのモジュールが異なる種類のエラーをチェックする、複数の型チェックモジュールを使用できます。この方法では、Javaの型システムのトップレベルにおいて、ユーザの望む時と場所で指定のチェックを加えて、ビルドをすることができます。

pluggable type checkersの存在とtype annotationsの適切な利用をすれば、エラーになりがちな事象を減らしつつ、強力なコードを書くことが出来ます。

多くの場合では、自前の型チェックモジュールを開発する必要はありません。サードバーティー製品が存在します。たとえば、ワシントン大学が開発したChecker Frameworkを活用できます。このフレームワークには、NonNullモジュール・正規表現と同等のモジュール・mutex lockモジュール、が含まれています。詳細については、The Checker Frameworkを参照してください。

Repeating Annotations

場合によっては型の使用場所や定義に複数のアノテーションを付与したいことがあります。Java SE 8 release以降では、repeating annotationsによって可能になります。

たとえば、ある種のスケジュールや与えられた時間にメソッドを実行する、UNIX cron サービスのような、タイマーサービスを使用するコードを書いている、とします。いま、doPeriodicCleanupメソッドの実行を、月末と毎週金曜日のp.m. 11:00として、タイマーにセットしたいとします。タイマーをセットするには、@Scheduleアノテーションを作成して、これを二つdoPeriodicCleanupに付与します。一つ目は月末を指定し、二つ目は毎週金曜日のp.m. 11:00で、以下のコードのようになります。

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }

上記の例はメソッドアノテーションを付与しています。標準アノテーションを使用するであろう場所ならどこでもアノテーションを複数付与可能です。たとえば、権限のないアクセス例外をハンドリングするクラスがあるとします。@Alertアノテーションをマネージャーとその他の管理者としてクラスに付与可能です。

@Alert(role="Manager")
@Alert(role="Administrator")
public class UnauthorizedAccessException extends SecurityException { ... }

互換性の理由により、repeating annotationsは、Javaコンパイラにより自動的に生成されるコンテナアノテーション(container annotation)に格納されています。これを実現するためにコンパイラでは*1、二つの宣言がコードに要求されます。

Step 1: Declare a Repeatable Annotation Type

アノテーションの型は@Repeatableメタアノテーションを付与する必要があります。カスタムScheduleサンプルを例にとると、

public @interface Schedule { ... }

コードは以下のようになります。

@Repeatable(Schedules.class)
public @interface Schedule { ... }

@Repeatableメタアノテーションの括弧内の値は、Javaコンパイラがrepeating annotationを格納するために生成するコンテナアノテーションの型になります。この例では、コンテナアノテーションの型はSchedulesなので、@Schedule repeating annotationが@Schedulesアノテーションに格納されます。

この宣言無しに同一アノテーションを付与することは、コンパイル時エラーを引き起こします。

Step 2: Declare the Containing Annotation Type

コンテナアノテーションは配列型のvalue要素を持つ必要があります。配列要素の型はrepeatable annotationの型でなければなりません。Schedulesコンテナアノテーションの宣言は以下のようになります。

public @interface Schedules {
    Schedule[] value;
}

Retrieving Annotations

リフレクションAPIで使用可能ないくつかのメソッドアノテーションを検索できます。AnnotatedElement.getAnnotationByType(Class)*2のようなメソッドの振る舞いは、単一のアノテーションを返し、引数に指定した型にアノテーションが一つだけの場合は単一のアノテーションを返すという点においては振る舞いは固定的です。型に複数個のアノテーションが指定されている場合、コンテナアノテーションから最初に取得したものが返されます。この方法では、レガシーコードは引き続き動作します。Java SE 8で導入された他のメソッド、たとえばgetAnnotationsByType(Class)などは、複数アノテーションを返すためにコンテナアノテーションをスキャンします。AnnotatedElementで利用可能な全メソッドについてはリンク先を参照してくださ。

Design Considerations

アノテーションを設計する際には、アノテーションcardinalityを考慮しなければいけません。アノテーションはゼロ回か一回付与できますが、もしアノテーション@Repeatableがマークされた場合は複数回付与できます。また、@Targetメタアノテーションを使用することでアノテーションの使用箇所を制限できます。たとえば、自前のrepeatable annotationをメソッドとフィールドでのみ使用可能にできます。プログラマが使用するアノテーションが可能な限り柔軟性かつ強力であることを保障するために、自前のアノテーションを注意深く設計することが重要です。

*1:In order for the compiler to do thisが上手く訳せない……

*2:見当たらないんで正式リリースする頃には無くなったんですかね?