Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

アノテーションをimplementsしたクラスを作成する

アノテーションを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()はそこに定義されています。

等価性

こんなコメントをいただけました!

インタフェースを実装したクラスのオブジェクトは、付与したアノテーションのオブジェクトとは異なるオブジェクトとなります。

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

こんなコメントもいただけました!

Weldのドキュメントに例がありました。

https://docs.jboss.org/weld/reference/latest/en-US/html/extend.html#_wrapping_an_literal_annotatedtype_literal

こんな実装です。

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クラスのアノテーションを処理する)"ため、とありました。