S-JIS[2007-11-10/2019-06-02] 変更履歴

アノテーション作成方法

JavaでJDK1.5から導入されたアノテーションを自分で作る方法。

アノテーションの使用方法(既存アノテーションの使用例)

アノテーション作成の基本

アノテーションは印を付ける為に使うので、データというものは持たない(実際は持てるけど)
なので、インターフェースのように定義する。

ExampleAnnotation.java:

package jp.hishidama.example;

public @interface ExampleAnnotation {
}

アノテーションを付ける場所の指定

特に何も指定しないと、作成したアノテーションは、アノテーションが使える場所ならどこにでも書くことが出来る。
“自作アノテーションが使える場所”を限定するには、自作アノテーション定義の直前に使える場所の印(Targetアノテーション)を付ける。

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)	//フィールドにだけ付けられる指定
public @interface LocationExampleAnnotation {
}

複数の場所を指定したい場合は@Targetの( )内に{ }を付けてカンマ区切りで複数の指定を列挙する。

@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR,
	  ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE })
public @interface LocationExampleAnnotation {
}
ElementType ver 説明 更新日
ElementType.TYPE 1.5 クラス定義やインターフェース定義(アノテーションやenumも含む)  
ElementType.FIELD 1.5 フィールド(メンバー変数)定義  
ElementType.METHOD 1.5 メソッド定義  
ElementType.PARAMETER 1.5 メソッドの引数の定義  
ElementType.CONSTRUCTOR 1.5 コンストラクター定義  
ElementType.LOCAL_VARIABLE 1.5 ローカル変数の定義  
ElementType.ANNOTATION_TYPE 1.5 アノテーション定義  
ElementType.PACKAGE 1.5 パッケージ  
ElementType.TYPE_PARAMETER 1.8 ジェネリクスの型引数定義 2014-03-20
ElementType.TYPE_USE 1.8 型(クラス)を使える場所全て(クラス定義やローカル変数等も含む) 2014-03-20

それぞれの指定の意味する場所は、以下のようになっている。

@LocationExampleAnnotation //TYPE:クラス定義やインターフェース定義をする場所
public class アノテーションを付けるサンプル {

	@LocationExampleAnnotation //FIELD:フィールド(メンバー変数)定義
	protected int value;

	@LocationExampleAnnotation //CONSTRUCTOR:コンストラクター定義
	public アノテーションを付けるサンプル() {
	}

	@LocationExampleAnnotation //METHOD:メソッド定義
	public void メソッド() {
	}

	public int 引数ありメソッド(
		@LocationExampleAnnotation //PARAMETER:パラメーター(メソッドの引数)定義
		int param
	) {

		@LocationExampleAnnotation //LOCAL_VARIABLE:ローカル変数定義
		int local_var = 0;

		for (@LocationExampleAnnotation int i = 0; i < 10; i++); //LOCAL_VARIABLE:ローカル変数定義[2008-12-08]

		//× メソッド呼び出しには付けられない
		System.out.println(local_var);

		//× 文には付けられない
		for (int i = 0; i< 10; i++) ;

		//× ブロックにも付けられない
		{
			int n = 1;
			System.out.println(n);
		}

		//× returnにも当然付けられない
		return 1;
	}
}

なお、デフォルトでは、1つの場所に同じアノテーションを複数書くことは出来ない。
が、JDK1.8以降では、@Repeatableを付けると、1つの場所にそのアノテーションを複数書くことが出来るようになる。[2014-03-20]

@Repeatableの使用方法


アノテーションの値

上記のTargetアノテーションのように、アノテーションには引数を渡すことが出来る。

@interface アノテーション名 {
	型 変数名();
	型 変数2();
	…
}

型には、プリミティブ型StringClass列挙型・アノテーション、あとはそれらの一次元配列のみ指定可能。[2008-07-05]

定義された変数が一個で、かつ変数名が「value」のときだけ、例2のように(例1のような「変数名 = 」という代入文の形式を)省略できる。

@interface StringValueAnnotation {
	String value();	//文字列を引数とする例
}

class UseExample {

	@StringValueAnnotaion(value = "文字列")	//値を指定する例1
	int n;
}

class UseExample2 {
	@StringValueAnnotaion("文字列")	//値を指定する例2
	int n;
}


アノテーションに引数が無い(引数を指定しない)ときは、使う際に丸括弧を省略できる。[2008-07-05]

	@LocationExampleAnnotation	…括弧を省略
	int field1;

	@LocationExampleAnnotation()	…括弧を付ける
	int field2;

取りうる値が決まるなら、列挙型を使うのが常套手段でしょう。

enum ExampleEnum { 値1, 値2, … }

@interface EnumAnnotation {
	ExampleEnum value();
}

@EnumAnnotation(value = ExampleEnum.値1)
class EnumUse {
}

取りうる値の型を配列にすると、複数の値を指定できるようになる。

@interface ValuesAnnotation {
	int[] value();
}

@ValuesAnnotation({ 1,2,3 })
class ValuesUse {
	@ValuesAnnotation(value = { 1,2,3,4 })
	int n;
}

この例の意味合いとしては「@ValuesAnnotation(value = new int[]{ 1,2,3,4 })」なんだと思うけど、この書き方は許されないらしい(コンパイルエラーになる)。

また、1つだけしか値を指定しない場合、とげ括弧{ }は省略できる。[2008-07-05]

	@ValuesAnnotation(value = 1)
	int m;

	@ValuesAnnotation(1)
	int o;

defaultを指定すると、アノテーションを指定する際の値指定を省略することが出来るようになる。

@interface DefaultAnnotation {
	String value() default "デフォルト値";
}
// デフォルト値を使用
@DefaultAnnotation class DefaultExample1 {
}

// デフォルト値を使用
@DefaultAnnotation() class DefaultExample2 {
}

// 値を指定
@DefaultAnnotation("普通に値指定") class DefaultExample3 {
}

アノテーションの有効範囲

アノテーションを付けたクラスでは、コンパイルしたclassファイル内にもそのアノテーションの情報が残る(デフォルトでは)。
指定次第でclassファイルから情報を消したり出来る。この指定はアノテーション定義毎に行う。

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.CLASS)
@interface ScopeAnnotation {
}
@Retentionの値 アノテーションを使える範囲 利用目的
RetentionPolicy.SOURCE ソース内のみ
コンパイルすると、classファイル内にはそのアノテーションの情報は残らない。
ソースを読み込む系統のツールが使用する?
RetentionPolicy.CLASS @Retentionが省略された場合のデフォルト値。
コンパイルしたclassファイル内にそのアノテーションの情報が保持されるが、実行時には読み込まれない。
classファイルを読み込む系統のツールが使用する?
例えばJavassistで読み込む事が出来る。
RetentionPolicy.RUNTIME 実行する際にもJavaVMにそのアノテーションの情報が読み込まれる。
リフレクションを用いてその情報を取得することが出来る。
実行時の情報取得。

アノテーション情報の実行時の取得

実行時にも有効になっているアノテーションの場合、実行中にリフレクションを用いてそのアノテーションの情報を取得することが出来る。

リフレクションの使用例 [2016-12-31]
アノテーションの定義例 アノテーションの使用例 リフレクションでの取得例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTypeAnnotation {
}
@MyTypeAnnotation
public class AnnotationExample {
}
Class<?> c = AnnotationExample.class;
for (Annotation a : c.getDeclaredAnnotations()) {
  System.out.println(a);
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyFieldAnnotation {
}
public class AnnotationExample {

  @MyFieldAnnotation
  public int field1;
}
Class<?> c = AnnotationExample.class;
Field f = c.getField("field1");
for (Annotation a : f.getDeclaredAnnotations()) {
  System.out.println(a);
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyMethodAnnotation {
}
public class AnnotationExample {

  @MyMethodAnnotation
  public void method1() {
  }
}
Class<?> c = AnnotationExample.class;
Method m = c.getMethod("method1");
for (Annotation a : m.getDeclaredAnnotations()) {
  System.out.println(a);
}
 
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface MyParameterAnnotation {
}
public class AnnotationExample {

  public void method2(@MyParameterAnnotation String arg) {
  }
}
Class<?> c = AnnotationExample.class;
Method m = c.getMethod("method2", String.class);
for (Parameter p : m.getParameters()) {
  System.out.println(p.getName());
  for (Annotation a : p.getDeclaredAnnotations()) {
    System.out.println(a);
  }
}
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
@interface MyConstructorAnnotation {
}
public class AnnotationExample {

  @MyConstructorAnnotation
  public AnnotationExample() {
  }
}
Class<?> c = AnnotationExample.class;
Constructor<?> t = c.getConstructor();
for (Annotation a : t.getDeclaredAnnotations()) {
  System.out.println(a);
}
@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyVariableAnnotation {
}
public class AnnotationExample {

  public void method3() {
    @MyVariableAnnotation
    String s = "";
  }
}
ローカル変数の取得方法は不明。
(たぶん実行時には取れない)
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnoTypeAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@MyAnnoTypeAnnotation
@interface AnnotationExample2 {
}
Class<?> c = AnnotationExample2.class;
for (Annotation a : c.getDeclaredAnnotations()) {
  System.out.println(a);
}
@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyPackageAnnotation {
}
パッケージのアノテーションの例 パッケージのアノテーションのリフレクションの例
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTypeParamAnnotation {
}
public class TypeParamExample<@MyTypeParamAnnotation T> {
}
Class<?> c = TypeParamExample.class;
for (TypeVariable<?> p : c.getTypeParameters()) {
  System.out.println(p.getName());
  for (Annotation a : p.getDeclaredAnnotations()) {
    System.out.println(a);
  }
}
public class AnnotationExample {

  public <@MyTypeParamAnnotation T> void method4(T arg) {
  }
}
Class<?> c = AnnotationExample.class;
Method m = c.getMethod("method4", Object.class);
for (TypeVariable<Method> p : m.getTypeParameters()) {
  System.out.println(p.getName());
  for (Annotation a : p.getDeclaredAnnotations()) {
    System.out.println(a);
  }
}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTypeUseAnnotation {
}
public class AnnotationExample3 extends @MyTypeUseAnnotation AnnotationExample {
}
Class<?> c = AnnotationExample3.class;
AnnotatedType s = c.getAnnotatedSuperclass();
for (Annotation a : s.getDeclaredAnnotations()) {
  System.out.println(a);
}
public class AnnotationExample4 implements @MyTypeUseAnnotation Serializable {
  private static final long serialVersionUID = 1L;
}
Class<?> c = AnnotationExample4.class;
for (AnnotatedType i : c.getAnnotatedInterfaces()) {
  System.out.println(i.getType());
  for (Annotation a : i.getDeclaredAnnotations()) {
    System.out.println(a);
  }
}
public @MyTypeUseAnnotation String method5() {
  return "";
}
Class<?> c = AnnotationExample.class;
Method m = c.getMethod("method5");
AnnotatedType r = m.getAnnotatedReturnType();
System.out.println(r.getType());
for (Annotation a : r.getDeclaredAnnotations()) {
  System.out.println(a);
}
public class AnnotationExample {

  public void method6() throws @MyTypeUseAnnotation IllegalStateException {
  }
}
Class<?> c = AnnotationExample.class;
Method m = c.getMethod("method6");
for (AnnotatedType e : m.getAnnotatedExceptionTypes()) {
  System.out.println(e.getType());
  for (Annotation a : e.getDeclaredAnnotations()) {
    System.out.println(a);
  }
}

アノテーションを取得できるクラス(Class・Field・Method・Parameter・AnnotatedType等)はAnnotatedElementインターフェースを実装しているので、そのメソッドを使うことが出来る。[2016-12-31]

メソッド ver 説明
boolean isAnnotationPresent(アノテーションクラス) 1.5 指定されたアノテーションが付いているかどうか。
アノテーション getAnnotation(アノテーションクラス) 1.5 指定されたアノテーションを返す。
指定されたアノテーションが付いていない場合はnullを返す。
Annotation[] getAnnotations() 1.5 付いているアノテーション一覧を返す。
アノテーション[] getAnnotationsByType(アノテーションクラス) 1.8  
アノテーション getDeclaredAnnotation(アノテーションクラス) 1.8  
アノテーション[] getDeclaredAnnotationsByType(アノテーションクラス) 1.8  
Annotation[] getDeclaredAnnotations() 1.5 直接付いているアノテーション一覧を返す。

リフレクションで、アノテーション固有の情報(値)を取得することも出来る。

アノテーション定義のサンプル:

@Retention(RetentionPolicy.RUNTIME)
public @interface StringAnnotation {
	String value();
}
@StringAnnotation("クラスだよーん")
public class サンプル {

	@StringAnnotation("コンストラクダ")
	public サンプル() {
	}

	@StringAnnotation("フィールドだでよ")
	public int n;

	@StringAnnotation("メッソド")
	public void function(
		@StringAnnotation("ひきすう") int param,
		@StringAnnotation("いんすう") int p2
	) {

		@StringAnnotation("ローカル変数ぞなもし")
		int a = 1;
	}
}

実行中にアノテーションの内容を表示する例:

	public static void main(String[] args) {
		Class clazz = サンプル.class;

		dump("クラス", clazz.getDeclaredAnnotations());

		Constructor[] cs = clazz.getConstructors();
		dump("コンストラクター", cs[0].getDeclaredAnnotations());

		Field[] fs = clazz.getDeclaredFields();
		dump("フィールド", fs[0].getDeclaredAnnotations());

		Method[] ms = clazz.getDeclaredMethods();
		dump("メソッド", ms[0].getDeclaredAnnotations());

		Annotation[][] ma = ms[0].getParameterAnnotations();
		dump("引数1", ma[0]);
		dump("引数2", ma[1]);

		//ローカル変数のアノテーションはどうやって取得するんだろう??
	}
	public static void dump(String message, Annotation[] as) {
		System.out.println(message);
		for (Annotation a : as) {
			dump1(a);
		}
	}
	public static void dump1(Annotation a) {
		if (a instanceof StringAnnotation) {
			StringAnnotation s = (StringAnnotation)a;
			System.out.println(s.value());
		}
	}

ここだけ見ると、インターフェースに定義された「value()」というメソッドを呼んでいるように見える。(普通にinstanceofも使えるし)
こう見えるようにする為に、アノテーションの値定義インターフェース定義っぽくしてあるんだろう。


Javassistでclassファイル内のアノテーションを取得する方法
→ちなみに、javapではアノテーションの情報は出てこない(片鱗は見えるんだけど、よく分からない…)


パッケージのアノテーション

アノテーションはパッケージに付けることも出来る。[2008-08-23]

パッケージに付けられるアノテーションの例:

package example.annotation.pack;

@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PackageAnnotation {
	String value();
}

パッケージ用アノテーションは、package-info.javaというソースファイル内のパッケージ文にしか付けることが出来ない。

src/example/annotation/package-info.java:

/**
 * パッケージにアノテーションを付ける実験
 */
@PackageAnnotation("パッケージのアノテーション")
package example.annotation;

import example.annotation.pack.PackageAnnotation; //この場合でも、importはpacakge文の後に書く

コンパイルすると、ちゃんとpackage-info.classが作られる。
Sunのjavacの場合、アノテーションが付いていないと、コンパイルしてもpackage-info.classは作られない。
 Eclipse3.2の場合は、packageに対してJavadocコメントかアノテーションが付いていれば作られる模様)
package-info.classの中身


なお、package-info.javaの中に、普通にクラスを定義したりすることも出来る。
ただし、public付きのクラスは定義できない。(publicの付いたクラスはソースファイル名と一致させる必要があり、package-infoというクラス名は定義できない(packageは予約語なので使えない・「-」は引き算の記号なので識別子には使えない)為)

/**
 * パッケージにアノテーションを2つ付ける実験
 */
@PackageAnnotation("パッケージのアノテーション")
@PackAnn("package-info内で定義したアノテーション")
package example.annotation;

import example.annotation.pack.PackageAnnotation;

//package-info.java内でアノテーションを定義してみる例
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PACKAGE)
@interface PackAnn {
	String value();
}

とは言え、あんまりここにプログラムを書くべきじゃないんだろうなー。


パッケージ用アノテーションでも、RetentionPolicy.RUNTIMEが付けられていれば、実行時にリフレクションでアノテーション情報を取得することが出来る。

	Package p = Package.getPackage("example.annotation");
	Annotation[] pa = p.getAnnotations();
	dump("パッケージ", pa);

実行結果:

@example.annotation.pack.PackageAnnotation(value=パッケージのアノテーション)
@example.annotation.PackAnn(value=package-info内で定義したアノテーション)

ちなみに、コンパイルされたpackage-info.classは、空のインターフェースとして作られている。
したがって、以下のようなリフレクションでpackage-infoを取得する事ができる。取得してどーすんだって気はしないでもないが(爆)

	Class c = Class.forName("example.annotation.package-info");
	System.out.println(c);

実行結果:

interface example.annotation.package-info

package-info.classをjavapコマンドで見れば、パッケージに付けられているアノテーションを確認できる。[2019-06-02]

$ javap -v package-info
〜
Constant pool:
〜
#8 = Utf8 Lexample/annotation/MyVersion;
#9 = Utf8 version
#10 = Utf8 version-example
〜
RuntimeInvisibleAnnotations:
0: #8(#9=s#10)

アノテーションのRetentionPolicyがRUNTIMEの場合はRuntimeVisibleAnnotations、CLASSの場合はRuntimeInvisibleAnnotationsの下にアノテーション情報が表示される。(SOURCEの場合は何も表示されない)
「#数字」は定数プールの値を指しており、上記の「#8(#9=s#10)」は「Lexample/annotation/MyVersion(version="version-example")」ということになるようだ。


ひとつの場所に同一アノテーションを複数指定する方法

JDK1.8より前は、1つの場所に同じアノテーションを複数書くことは出来なかった。[2014-03-20]
JDK1.8以降では、@Repeatableを付けてアノテーションを定義すると、1つの場所にそのアノテーションを複数書くことが出来るようになる。

@Repeatableを使うには、「複数指定したいアノテーション」と「そのアノテーションを複数個保持するアノテーション」をペアで定義する必要がある。

import java.lang.annotation.Repeatable;
/** 
 * 同一箇所に複数指定可能なアノテーション
 */
@Repeatable(RepeatValueHolderAnnotation.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatExampleAnnotation {

	public String value();
}
/**
 * 「複数指定可能なアノテーション」のデータを実際に複数保持する為のアノテーション
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatValueHolderAnnotation {

	public RepeatExampleAnnotation[] value();
}

@Repeatableは、複数指定したいアノテーションに付ける。
@Repeatableの引数には、“複数指定したアノテーションの情報を実際に保持する為のアノテーション”のクラスを指定する。

この“複数指定したアノテーションの情報を実際に保持する為のアノテーション”では、value()の型を“複数指定したいアノテーションの配列”として定義する。

参考: JavaチュートリアルのRepeating Annotations


上記のようにして定義したアノテーションは、以下のようにして使うことが出来る。

@RepeatExampleAnnotation("abc")
@RepeatExampleAnnotation("def")
public class RepeatExample {
}

同一箇所に複数定義されたアノテーションは、リフレクションで以下のようにして取得することが出来る。

取得される情報 備考
Annotation[] as = RepeatExample.class.getAnnotations(); RepeatValueHolderAnnotation(value = [
  RepeatExampleAnnotation(value = "abc"),
  RepeatExampleAnnotation(value = "def")
])
RepeatExampleに指定されているアノテーションが直接取れるのではなく、
それらを保持しているアノテーションが取れる。
RepeatValueHolderAnnotation[] as = RepeatExample.class.getAnnotationsByType(RepeatValueHolderAnnotation.class); 同上 getAnnotationsByType()だと、引数で指定されたアノテーションクラスのものだけ取得できる。
RepeatValueHolderAnnotation a = RepeatExample.class.getAnnotation(RepeatValueHolderAnnotation.class); 同上  
RepeatExampleAnnotation[] as = RepeatExample.class.getAnnotationsByType(RepeatExampleAnnotation.class); [
  RepeatExampleAnnotation(value = "abc"),
  RepeatExampleAnnotation(value = "def")
]
getAnnotationsByType()だと、RepeatExampleに直接指定されたアノテーションを取得することが出来る。
RepeatExampleAnnotation a = RepeatExample.class.getAnnotation(RepeatExampleAnnotation.class); null getAnnotation()では、複数指定されたアノテーションを取得することは出来ない。

要するに、ソース上は複数のアノテーションを直接指定しているように見えるが、
実際には暗黙的に“複数データを保持するアノテーション”が指定され、その内部データとして複数のアノテーションを保持しているようだ。


Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま