Javaのアノテーションを独自で定義する
徐々に利用する機会が増えてきたアノテーションだが、アノテーションベースのフレームワークで使われているのを使ったりすることはあっても自分でアノテーションを定義したことが無かった。
「業務用フレームワーク作る際も使えそうだよなぁ」と思っていたので、今後使いこなす為にも改めてちゃんと勉強しなおしてみた。
※「アノテーション」自体は、JUnit、Jaxb、Spring等によって使い慣れていることを前提。
アノテーション型
独自のアノテーションを作成する為には、以下のようなアノテーション型のクラスを作成する必要がある。
@interface Annotation名{ キーの型 キーの名前(); ... }
クラス名がアノテーション名、メソッド名がキー名となり、メソッドの返り値がキーに指定できる型となる。
アノテーション型はインターフェイス型と似ているが、通常のインターフェイス型に比べ、以下の制限がある。
- extends節を書けない
- メソッドは引数とthrows節を持てない
- メソッドの戻り値で使えるのはプリミティブ型、String型、Class型、enum定数、アノテーション型、それらの配列、のみ
- ジェネリクスは使えない
なお、@interfaceが指定されているクラスは、暗黙的ににjava.lang.annotation.Annotationインタフェースの継承クラスになる。
独自のアノテーション定義例
public @interface Hatena { int priority(); String user(); } public enum DiaryType {CATEGORY, DAY, NEW} @interface Diary { DiaryType value(); }
上記の場合は @Hatena と @Diary がアノテーションとして指定できるようになる。
この独自アノテーションを利用した例を以下に示す。
上記の独自定義クラスの利用例
@Hatena(priority = 1, user = "valice") public class SampleClass { public SampleClass(){} @Diary(DiaryType.CATEGORY) public void sampleMethod(){ ...省略 } }
メタアノテーション型Target
アノテーションを定義する場合に重要な点は2つあり
- 対象となる構文要素は何か(コンストラクタ、メソッド、ローカル変数等)
- どのような情報を記述できるのか
を記述する必要があるが、「対象となる構文要素は何か」に関しては省略することができ、省略した場合は構文要素の制限が無い状態になる。
最初の例のアノテーション定義ではそれらを省略しており、@Hatena と @Diaryはアノテーションを指定できる要素であればいかなる要素に対しても付加できることになる。
アノテーションを付加できる構文要素を制限したい場合は、メタアノテーション型Targetをを付与すればよい。
@Hatenaはクラス、@Diaryはメソッドにのみ付与できるようにしたい場合、以下のようになる。
独自のアノテーション定義例にTagetを利用したもの
@Target(ElementType.TYPE) public @interface Hatena { int priority(); String user(); } public enum DiaryType {CATEGORY, DAY, NEW} @Target(ElementType.METHOD) @interface Diary { DiaryType value(); }
ElementTypeで提供されている定数
ElementTypeは列挙型で、指定できる定数は以下。
定数名 | 構文要素 |
---|---|
ElementType.ANNOTATION_TYPE | アノテーション型宣言 |
ElementType.CONSTRUCTOR | コンストラクタ |
ElementType.FIELD | フィールド宣言、enum宣言 |
ElementType.LOCAL_VARIABLE | ローカル変数宣言 |
ElementType.METHOD | メソッド宣言 |
ElementType.PACKAGE | パッケージ宣言 |
ElementType.PARAMETER | 引数宣言 |
ElementType.TYPE | クラス宣言、インタフェース宣言、アノテーション型宣言、enum宣言 |
@Targetアノテーションのvalueキーは配列である為、以下の様に複数指定することができる。その場合はそれら全ての構文要素に付与できるようになる。
@Target({ElementType.TYPE, ElementType.METHOD}) public @interface Hatena { int priority(); String user(); }
デフォルト値
キーが複数ある場合でも、デフォルト値を指定することによってそのキーの指定を省略することができる。
public @interface Hatena { int priority() default 1; String user(); } @Hatena(user = "valice") public class SampleClass { ... }
マーカ・アノテーション
アノテーション型の定義においてメソッドが一つも無い場合、そのようなアノテーションはマーカ・アノテーションと呼ばれる。
マーカ・アノテーションは使用する際、以下のように括弧を省略することができる。
public @interface MarkerAnnotation {} @MarkerAnnotation public class SampleClass { ... }
これらは、標準アノテーションの@Overrideや@Deprecatedと同じ振る舞いである。(@Overrideや@Deprecatedもマーカ・アノテーションである)
その他のメタアノテーション型
メタアノテーション型Targetは既に紹介したが、その他の標準メタアノテーションであるRetention、Inherited、Documentedについても記載しておく。
Retention
アノテーションの有効範囲を指定できる。
定数名 | 有効範囲 |
---|---|
RetentionPolicy.CLASS | アノテーションの情報をクラス・ファイルに出力するが、実行時JVMにロードされない。省略された場合のデフォルト値。 |
RetentionPolicy.RUNTIME | アノテーションの情報をクラス・ファイルに出力し、実行時JVMにもロードされる。その為、リフレクションAPI経由でその情報にアクセスできる。 |
RetentionPolicy.SOURCE | アノテーションの情報はソース・コード上のみに保持され、コンパイル時によって破棄される。 |
@Retention(RetentionPolicy.CLASS) public @interface Hatena { int priority(); String user(); }
※Retentionはキーがvalueしか無いため、キー名の省略が可能
Inherited
アノテーションを利用したクラスにおいて、そのアノテーション指定を子孫クラスに継承させることができる。
指定は必ずクラス定義に対してする必要があり、それ以外に指定した場合は無効となる。
下記の例の場合、SampleSubClassにおいても@Hatenaアノテーションが有効となっている。
@Inherited public @interface Hatena { int priority(); String user(); } @Hatena(priority = 1, user = "valice") public class SampleClass { ... } public class SampleSubClass extends { ... }
※Inheritedはマーカ・メタアノテーションの為、括弧は省略可能
予告
今回は定義の仕方だけですが、独自のアノテーションの利用方法もそのうち書く予定です。その際はここにリンクを貼ります。