Java アノテーションに関する基本知識
[履歴] [最終更新] (2017/05/13 17:58:49)
最近の投稿
注目の記事

概要

Java SE 5 から導入されたアノテーション機能に関する基本事項をまとめます。アノテーションは @Xxx の形式で、Java の言語機能としては表現できない補足情報を注釈 (annotation) として記述するための仕組みです。クラス、インターフェース、メソッド、メンバ変数、メソッド引数などの定義箇所に記述します。

アノテーションの作成

アノテーションを利用する場面としては、自分で定義することは少なく、主に Java 言語側が定義する、@Override, @Deprecated, @SuppressWarnings といった標準アノテーションや、フレームワークや外部ライブラリが定義するアノテーションを利用することの方が多いかもしれません。外部で定義されたアノテーションを利用する際の理解を深める目的で、簡単なアノテーションを自分で定義してみます。

アノテーションの種類

アノテーションは用途によって 2 種類存在します。

  • マーカーアノテーション → データを持たせることができず、何らかの用途でマークするために利用
  • 単一値アノテーション、フルアノテーション → データを持たせることができるアノテーション

マーカーアノテーション

以下のようなファイルを用意します。Eclipse の場合は右クリック → New → Annotation で追加できます。@interface を記述することでアノテーションを定義できます。アノテーション定義のために、アノテーションを利用していることになります。このような、アノテーションを定義するときに付与するアノテーションを特にメタアノテーションとよびます。

MyAnnotation.java

package myproject;

public @interface MyAnnotation {
}

定義したアノテーションは、クラス、インターフェース、メソッド、メンバ変数、メソッド引数などの定義箇所に記述して利用します。利用箇所を制限するためには、アノテーション定義時に @Target メタアノテーションを記載します。以下のように ElementType.METHOD を利用すると、メソッド定義箇所だけで使用できるアノテーションが定義できます。

package myproject;

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

@Target(ElementType.METHOD)
public @interface MyAnnotation {
}

利用例

@MyAnnotation
public void MyMethod() {
}

メタアノテーションについて

前述の @Target や後述の @Retention の他に、継承関係にある二つのクラスがあるとき、親クラスに付与すると自動的に子クラスにも付与されたような状況になるアノテーションを定義するための @Inherited があります。クラスのアノテーションにだけ使用できます

付与されたクラスの javadoc 生成時に、アノテーションで補足した情報をクラスのドキュメントに含めるかどうかを制御する @Documented メタアノテーションもよく使用されます。

単一値アノテーション

以下のように String value() を記述すると、値を一つだけ保持できるアノテーションを定義できます。後述のフルアノテーションと似ていますが、value は特別な意味を有するということになります。

package myproject;

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

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value();
}

利用例

@MyAnnotation("my setting")
public void MyMethod() {
}

保持する値を制限したい場合は enum を利用します。

package myproject;

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

enum MyEnum {
    val1,
    val2
}

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    MyEnum value();
}

利用例

@MyAnnotation(MyEnum.val1)
public void MyMethod() {
}

フルアノテーションではないですが、単一の配列を保持できるようにすることで、複数の値を保持する単一アノテーションを定義できます。

package myproject;

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

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String[] value();
}

利用例1

@MyAnnotation({"str1", "str2"})
public void MyMethod() {
}

利用例2

@MyAnnotation("str1")
public void MyMethod() {
}

アノテーションには既定値を定義できます。

package myproject;

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

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "my default";
}

利用例

@MyAnnotation
public void MyMethod() {
}

フルアノテーション

value ではなく他の名前を使用して、複数の値を保持できるアノテーションを定義できます。

package myproject;

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

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String myattr1();
    int myattr2();
}

利用例

@MyAnnotation(myattr1 = "string", myattr2 = 123)
public void MyMethod() {
}

Retention 保持期間

既定では、アノテーションを付与して定義されたクラスやメソッドをコンパイルすると、アノテーション注釈で補足した情報はコンパイル結果に含まれますが、実行時には JVM から読み込まれません。これを変更するためには、アノテーション定義時に @Retention メタアノテーションを利用します。コンパイルすると情報が消失する SOURCE や、コンパイル結果に含まれるだけでなく JVM で実行するときにも読み込まれる RUNTIME が指定できます。

package myproject;

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

@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.CLASS) // 既定値
//@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

特に @Retention(RetentionPolicy.RUNTIME) を指定した場合は以下のように実行時に利用できます。こちらのページに記載した、クラスの構造を実行中に読み取る「リフレクション」を利用します。

Main.java

package myproject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class Main {

    public static void main(String[] args) {
        Class<Main> clazz = Main.class;

        try {
            Method method = clazz.getMethod("MyMethod");

            // 一覧を取得
            for(Annotation annotation : method.getDeclaredAnnotations()) {
                System.out.println(annotation);
            }

            // 引数で指定して取得
            MyAnnotation annotation = method.getDeclaredAnnotation(MyAnnotation.class);
            System.out.println(annotation);
            if(annotation.value() == 123) {
                System.out.println("do something...");
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }

    @MyAnnotation(123)
    public void MyMethod() {
    }
}

MyAnnotation.java

package myproject;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    int value();
}

実行例

@myproject.MyAnnotation(value=123)
@myproject.MyAnnotation(value=123)
do something...

標準アノテーションについて

標準アノテーションは多数提供されています。その中でも特に、以下のような標準アノテーションがよく使用されます。

  • @Override 親クラスのメソッドを上書きしていることを明記することで、視認性がよくなるだけでなく、タイプミス等で親クラスに存在しないメソッドを新規作成した際にエラーを発生させます。
  • @Deprecated 非推奨メソッドであることを明記することで、使用するとコンパイル時に警告が出るようになります。
  • @SuppressWarnings 単一値アノテーションです。引数に指定した警告について、アノテーションを付与した箇所で発生するコンパイル時の警告がログに出力されなくなります。@Deprecated メソッドを使用している場合など、警告されていることは把握済みであっても解消することが難しい場合で、コンパイルログの視認性をよくしたい場合などに利用します。@SuppressWarnings("deprecation")@SuppressWarnings("unused") 等のように使用します。

javadoc におけるアノテーション

javadoc におけるアノテーションは多数提供されています。その中でも特に、以下のようなアノテーションがよく使用されます。Eclipse の場合は File → Export → Java → Javadoc → Next → Finish で生成できます。

/**
 * サンプルコメント
 * @author 氏名
 * @param myarg1 引数1 の説明
 * @param myarg2 引数2 の説明
 * @return 返り値の説明
 * @exception IOException メソッド内で発生する例外の説明
 * @version メソッドやクラスのバージョン
 * @see Main#MyMethod(int, int) 関連箇所の説明
 */
@Deprecated
@MyAnnotation(123)
public int MyMethod(int myarg1, int myarg2) throws IOException {
    return 123;
}
関連ページ
    概要 Lombok というライブラリを導入すると、Getter/Setter といったよく記述するコードをアノテーションだけで自動生成できます。Google Guava と同様に、Java の冗長なコードを削減する効果があります。簡単な使い方をまとめます。 インストール こちらのページの情報をもとにインストールします。
    概要 Spring は様々なフレームワークを提供する、Java のプロジェクト群です。Spring Batch、Spring Security、Spring Loaded といったプロジェクトがあります。Spring Boot は、これら Spring フレームワークを内部的に利用するフレームワークです。効率的なアプリケーション開発が可能になります。環境構築の手順を把握できるサンプルコードをまと