アノテーションをimplementsしたクラスを作成できるというのを最近知りました。アノテーションなんてJDK 1.5からあるのに、知らないもんですね…
みなさんはご存知ですか?とTwitterにツイートしたところ、ご存知の方もたくさんいらっしゃいましたが、ご存知ない方もたくさんいらっしゃった、という感じです。
見てみましょう。こういうアノテーションがあるとします。
@interface SampleAnnotation {
String sample();
}
SampleAnnotationをimplementsしたSampleAnnotationImplクラスを作ります。要は、キーワードの記述通りアノテーションもインタフェースということです。
import java.lang.annotation.Annotation; class SampleAnnotationImpl implements SampleAnnotation { @Override public Class<? extends Annotation> annotationType() { return SampleAnnotation.class; } @Override public String sample() { return "sample"; } }
コンパイルできました。アノテーションに定義した属性はメソッドなので、ここではsample()も実装しなければなりません。annotationType()メソッドはアノテーションの型を返します。
javap
するとよりはっきりします。
$ javap SampleAnnotation interface SampleAnnotation extends java.lang.annotation.Annotation { public abstract java.lang.String sample(); }
SampleAnnotationはインタフェースであり、java.lang.annotation.Annotationを継承しています。annotationType()はそこに定義されています。
等価性
こんなコメントをいただけました!
アノテーションを実装する場合は equals / hashCode を実装してねという話
— SpringBoot になったもちだ (@mike_neck) 2018年8月24日
インタフェースを実装したクラスのオブジェクトは、付与したアノテーションのオブジェクトとは異なるオブジェクトとなります。
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) @interface SampleAnnotation { String sample(); } import java.lang.annotation.Annotation; @SampleAnnotation(sample="sample") public class Sample { public static void main(String[] args) { Annotation a1 = Sample.class.getAnnotation(SampleAnnotation.class); Annotation a2 = new SampleAnnotationImpl(); System.out.println("a1.equals(a2): " + a1.equals(a2)); System.out.println("a2.equals(a1): " + a2.equals(a1)); } }
実行時に取得できるようアノテーションに@Retention
を追加しました。
$ java Sample a1.equals(a2): true a2.equals(a1): false
考えてみるとたしかにそうなのですが、等価である必要があるなら、equals()/hashCode()を実装せよ、ということです。
いつ使うのか?
バイトコード操作
私がこれを知ったのは、バイトコード操作ライブラリで動的にアノテーションを付与していたコードからでした。実行時に動的にクラスを作成してアノテーションを付与する場合に、APIにアノテーションオブジェクトを渡す必要がありました。Byte Buddyです。
new ByteBuddy() .subclass(Object.class) .annotateType(new SampleAnnotationImpl()) .make();
CDI Extension
こんなコメントもいただけました!
CDIをコテコテに拡張する時に自分でアノテーションのインスタンスを作らないといけないケースがあったような気がします(遠い記憶でうろ覚えですが……)
— うらがみ⛄ (@backpaper0) 2018年8月23日
CDI Extensionでアノテーションくっつける時とか?
— かずひら (@kazuhira_r) August 23, 2018
Weldのドキュメントに例がありました。
こんな実装です。
class NamedLiteral extends AnnotationLiteral<Named> implements Named { @Override public String value() { ... return qualifiedName; } }
Java EEの@Named
アノテーションを実装しています。AnnotationLiteralクラスは"Supports inline instantiation of annotation type instances.(アノテーション型のインラインでのインスタンス化をサポートする)"とあります。AnnotationLiteralクラスではequals/hashCodeの実装や便利メソッドの提供、戦術のannotationType()メソッドの実装がありました。
public Class<? extends Annotation> annotationType() { if (annotationType == null) { Class<?> annotationLiteralSubclass = getAnnotationLiteralSubclass(this.getClass()); if (annotationLiteralSubclass == null) { throw new RuntimeException(getClass() + " is not a subclass of AnnotationLiteral"); } annotationType = getTypeParameter(annotationLiteralSubclass); if (annotationType == null) { throw new RuntimeException(getClass() + " does not specify the type parameter T of AnnotationLiteral<T>"); } } return annotationType; }
端的にはジェネリクスで指定したアノテーションの型を返します。
このCDI Extensionは"process the annotations of a bean class before the container builds its metamodel(コンテナがそのメタモデルをビルドする前にBeanクラスのアノテーションを処理する)"ため、とありました。