S-JIS[2007-11-10/2014-03-20] 変更履歴

アノテーション作成方法

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

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

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

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

SampleAnnotation.java:

package jp.hishidama.sample;

public @interface SampleAnnotation {
}

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

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

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

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

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

@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR,
	  ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE })
public @interface PlaceSampleAnnotation {
}

→ElementType.PACKAGE

JDK1.8で、TYPE_PARAMETERとTYPE_USEが追加になった。[2014-03-20]


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

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

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

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

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

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

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

		for (@PlaceSampleAnnotation 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 UseSample {

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

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


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

	@PlaceSampleAnnotation	…括弧を省略
	int field1;

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

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

enum SampleEnum { 値1, 値2, … }

@interface EnumAnnotation {
	SampleEnum value();
}

@EnumAnnotation(value = SampleEnum.値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 DefaultSample1 {
}

@DefaultAnnotation() class DefaultSample2 {
}

@DefaultAnnotation("普通に値指定") class DefaultSample3 {
}

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

アノテーションを付けたクラスでは、コンパイルした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にそのアノテーションの情報が読み込まれる。リフレクションを用いてその情報を取得することが出来る。 実行時の情報取得。

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

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

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

@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) {
			System.out.println(a);
		}
	}

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


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

	public static void dump(Annotation a) {
		if (a instanceof StringAnnotation) {
			StringAnnotation s = (StringAnnotation)a;
			System.out.println(s.value());
		}
	}

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


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

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

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

package sample.annotation.pack;

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

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

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

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

import sample.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 sample.annotation;

import sample.annotation.pack.PackageAnnotation;

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

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


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

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

実行結果:

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

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

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

実行結果:

interface sample.annotation.package-info

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

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目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま