Java SE 5 から導入されたアノテーション機能に関する基本事項をまとめます。アノテーションは @Xxx
の形式で、Java の言語機能としては表現できない補足情報を注釈 (annotation) として記述するための仕組みです。クラス、インターフェース、メソッド、メンバ変数、メソッド引数などの定義箇所に記述します。
アノテーションを利用する場面としては、自分で定義することは少なく、主に Java 言語側が定義する、@Override
, @Deprecated
, @SuppressWarnings
といった標準アノテーションや、フレームワークや外部ライブラリが定義するアノテーションを利用することの方が多いかもしれません。外部で定義されたアノテーションを利用する際の理解を深める目的で、簡単なアノテーションを自分で定義してみます。
アノテーションは用途によって 2 種類存在します。
以下のようなファイルを用意します。Eclipse の場合は右クリック → New → Annotation で追加できます。@interface
を記述することでアノテーションを定義できます。アノテーション定義のために、アノテーションを利用していることになります。このような、アノテーションを定義するときに付与するアノテーションを特にメタアノテーションとよびます。
MyAnnotation.java
package myproject;
public @interface MyAnnotation {
}
定義したアノテーションは、クラス、インターフェース、メソッド、メンバ変数、メソッド引数などの定義箇所に記述して利用します。利用箇所を制限するためには、アノテーション定義時に @Target
メタアノテーションを記載します。以下のように ElementType.METHOD
を利用すると、メソッド定義箇所だけで使用できるアノテーションが定義できます。
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface MyAnnotation {
}
利用例
@MyAnnotation
public void MyMethod() {
}
前述の @Target
や後述の @Retention
の他に、継承関係にある二つのクラスがあるとき、親クラスに付与すると自動的に子クラスにも付与されたような状況になるアノテーションを定義するための @Inherited
があります。クラスのアノテーションにだけ使用できます。
付与されたクラスの javadoc 生成時に、アノテーションで補足した情報をクラスのドキュメントに含めるかどうかを制御する @Documented
メタアノテーションもよく使用されます。
以下のように String value()
を記述すると、値を一つだけ保持できるアノテーションを定義できます。後述のフルアノテーションと似ていますが、value
は特別な意味を有するということになります。
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value();
}
利用例
@MyAnnotation("my setting")
public void MyMethod() {
}
保持する値を制限したい場合は enum を利用します。
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
enum MyEnum {
val1,
val2
}
@Target(ElementType.METHOD)
public @interface MyAnnotation {
MyEnum value();
}
利用例
@MyAnnotation(MyEnum.val1)
public void MyMethod() {
}
フルアノテーションではないですが、単一の配列を保持できるようにすることで、複数の値を保持する単一アノテーションを定義できます。
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String[] value();
}
利用例1
@MyAnnotation({"str1", "str2"})
public void MyMethod() {
}
利用例2
@MyAnnotation("str1")
public void MyMethod() {
}
アノテーションには既定値を定義できます。
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "my default";
}
利用例
@MyAnnotation
public void MyMethod() {
}
value
ではなく他の名前を使用して、複数の値を保持できるアノテーションを定義できます。
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String myattr1();
int myattr2();
}
利用例
@MyAnnotation(myattr1 = "string", myattr2 = 123)
public void MyMethod() {
}
既定では、アノテーションを付与して定義されたクラスやメソッドをコンパイルすると、アノテーション注釈で補足した情報はコンパイル結果に含まれますが、実行時には JVM から読み込まれません。これを変更するためには、アノテーション定義時に @Retention
メタアノテーションを利用します。コンパイルすると情報が消失する SOURCE
や、コンパイル結果に含まれるだけでなく JVM で実行するときにも読み込まれる RUNTIME
が指定できます。
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.CLASS) // 既定値
//@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
特に @Retention(RetentionPolicy.RUNTIME)
を指定した場合は以下のように実行時に利用できます。こちらのページに記載した、クラスの構造を実行中に読み取る「リフレクション」を利用します。
Main.java
package myproject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
Class<Main> clazz = Main.class;
try {
Method method = clazz.getMethod("MyMethod");
// 一覧を取得
for(Annotation annotation : method.getDeclaredAnnotations()) {
System.out.println(annotation);
}
// 引数で指定して取得
MyAnnotation annotation = method.getDeclaredAnnotation(MyAnnotation.class);
System.out.println(annotation);
if(annotation.value() == 123) {
System.out.println("do something...");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
@MyAnnotation(123)
public void MyMethod() {
}
}
MyAnnotation.java
package myproject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int value();
}
実行例
@myproject.MyAnnotation(value=123)
@myproject.MyAnnotation(value=123)
do something...
標準アノテーションは多数提供されています。その中でも特に、以下のような標準アノテーションがよく使用されます。
@Override
親クラスのメソッドを上書きしていることを明記することで、視認性がよくなるだけでなく、タイプミス等で親クラスに存在しないメソッドを新規作成した際にエラーを発生させます。@Deprecated
非推奨メソッドであることを明記することで、使用するとコンパイル時に警告が出るようになります。@SuppressWarnings
単一値アノテーションです。引数に指定した警告について、アノテーションを付与した箇所で発生するコンパイル時の警告がログに出力されなくなります。@Deprecated
メソッドを使用している場合など、警告されていることは把握済みであっても解消することが難しい場合で、コンパイルログの視認性をよくしたい場合などに利用します。@SuppressWarnings("deprecation")
、@SuppressWarnings("unused")
等のように使用します。javadoc におけるアノテーションは多数提供されています。その中でも特に、以下のようなアノテーションがよく使用されます。Eclipse の場合は File → Export → Java → Javadoc → Next → Finish で生成できます。
/**
* サンプルコメント
* @author 氏名
* @param myarg1 引数1 の説明
* @param myarg2 引数2 の説明
* @return 返り値の説明
* @exception IOException メソッド内で発生する例外の説明
* @version メソッドやクラスのバージョン
* @see Main#MyMethod(int, int) 関連箇所の説明
*/
@Deprecated
@MyAnnotation(123)
public int MyMethod(int myarg1, int myarg2) throws IOException {
return 123;
}