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]
上記のTargetアノテーションのように、アノテーションには引数を渡すことが出来る。
@interface アノテーション名 { 型 変数名(); 型 変数2(); … }
型には、プリミティブ型・String・Class・列挙型・アノテーション、あとはそれらの一次元配列のみ指定可能。[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にそのアノテーションの情報が読み込まれる。 リフレクションを用いてその情報を取得することが出来る。 |
実行時の情報取得。 |
実行時にも有効になっているアノテーションの場合、実行中にリフレクションを用いてそのアノテーションの情報を取得することが出来る。
アノテーションの定義例 | アノテーションの使用例 | リフレクションでの取得例 |
---|---|---|
@Target(ElementType.TYPE) |
@MyTypeAnnotation |
Class<?> c = AnnotationExample.class; |
@Target(ElementType.FIELD) |
public class AnnotationExample { |
Class<?> c = AnnotationExample.class; |
@Target(ElementType.METHOD) |
public class AnnotationExample { |
Class<?> c = AnnotationExample.class; |
@Target(ElementType.PARAMETER) |
public class AnnotationExample { |
Class<?> c = AnnotationExample.class; |
@Target(ElementType.CONSTRUCTOR) |
public class AnnotationExample { |
Class<?> c = AnnotationExample.class; |
@Target(ElementType.LOCAL_VARIABLE) |
public class AnnotationExample { |
ローカル変数の取得方法は不明。 (たぶん実行時には取れない) |
@Target(ElementType.ANNOTATION_TYPE) |
@Retention(RetentionPolicy.RUNTIME) |
Class<?> c = AnnotationExample2.class; |
@Target(ElementType.PACKAGE) |
→パッケージのアノテーションの例 | →パッケージのアノテーションのリフレクションの例 |
@Target(ElementType.TYPE_PARAMETER) |
public class
TypeParamExample<@MyTypeParamAnnotation T> { |
Class<?> c = TypeParamExample.class; |
public class AnnotationExample { |
Class<?> c = AnnotationExample.class; |
|
@Target(ElementType.TYPE_USE) |
public class AnnotationExample3 extends
@MyTypeUseAnnotation AnnotationExample { |
Class<?> c = AnnotationExample3.class; |
public class AnnotationExample4 implements
@MyTypeUseAnnotation Serializable { |
Class<?> c = AnnotationExample4.class; |
|
public @MyTypeUseAnnotation String method5() { |
Class<?> c = AnnotationExample.class; |
|
public class AnnotationExample { |
Class<?> c = AnnotationExample.class; |
アノテーションを取得できるクラス(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 = [ |
RepeatExampleに指定されているアノテーションが直接取れるのではなく、 それらを保持しているアノテーションが取れる。 |
RepeatValueHolderAnnotation[] as = RepeatExample.class.getAnnotationsByType(RepeatValueHolderAnnotation.class); |
同上 | getAnnotationsByType()だと、引数で指定されたアノテーションクラスのものだけ取得できる。 |
RepeatValueHolderAnnotation a = RepeatExample.class.getAnnotation(RepeatValueHolderAnnotation.class); |
同上 | |
RepeatExampleAnnotation[] as = RepeatExample.class.getAnnotationsByType(RepeatExampleAnnotation.class); |
[ |
getAnnotationsByType()だと、RepeatExampleに直接指定されたアノテーションを取得することが出来る。 |
RepeatExampleAnnotation a = RepeatExample.class.getAnnotation(RepeatExampleAnnotation.class); |
null |
getAnnotation()では、複数指定されたアノテーションを取得することは出来ない。 |
要するに、ソース上は複数のアノテーションを直接指定しているように見えるが、
実際には暗黙的に“複数データを保持するアノテーション”が指定され、その内部データとして複数のアノテーションを保持しているようだ。