【Java】 アノテーションを自分で定義してみる

2020年9月23日

はじめに

お世話になります、hosochinです

さて、今回は
アノテーションを自作
してみようと思います

具体的なサンプルコードの前にメタアノテーションについて軽く説明します

メタアノテーション

メタアノテーションとはアノテーションの宣言自体を修飾するアノテーションです
アノテーションを自分で定義する場合はこのメタアノテーションを使います

標準メタアノテーション

標準メタアノテーションについて以下に示します

アノテーションの種類説明
Targetアノテーションの対象の要素を指定
Retentionアノテーションの存在期間を指定
Documentedアノテーションを文書化対象するjavadocへの指定
Inheritedアノテーションが自動的に継承されることを指定
Repeatable同じ対象要素に同じアノテーションを複数指定可能

Targetについて

アノテーションの対象要素を次のように指定します

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

ElementTypeには以下の種類があります

ElementTypeの種類説明
ANNOTATION_TYPE アノテーション定義に使える
CONSTRUCTORコンストラクタ定義に使える
FIELDフィールド定義に使える
LOCAL_VARIABLEローカル変数定義に使える
METHODメソッド定義に使える
PACKAGEパッケージ定義に使える
PARAMETERメソッドのパラメータ定義に使える
TYPEクラス、インタフェース(アノテーション、enum含む)定義に使える
TYPE_PARAMETER型パラメータ定義に使える
TYPE_USE型を使える場所全てに使える

Retentionについて

アノテーションの存在期間を次のように指定します

@Retention(RetentionPolicy.RUNTIME)
public @interface TutorialAnnotation { }

RetentionPolicyには以下の種類があります

RetentionPolicyの種類説明
SOURCE        アノテーションはコンパイラによって破棄される
CLASSアノテーションはコンパイラによってクラスファイルに記録される(デフォルトの設定)
RUNTIMEアノテーションはコンパイラによってクラスファイルに記録され、実行時に参照可能

サンプルコード

  • アノテーションを定義
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}
  • MyAnnotationアノテーションをクラスに付与
@MyAnnotation(value = "sample")
public class Sample {
}
  • リフレクションを使って呼び出す
public class Main {
    public static void main(String[] args) throws Exception {
        // リフレクションでクラス名を探す
        Class<?> targetClass = Class.forName("Sample");
        // MyAnnotationアノテーションを探す
        MyAnnotation myAnnotation = targetClass.getAnnotation(MyAnnotation.class);
        System.out.println(myAnnotation.value());
    }
}
  • 実行結果
sample

アノテーションでコールバックパターン

自作アノテーションをちょっと応用してみて、コールバックパターンを書いてみます
今回は文字列をCamelケースからSnakeケースに、小文字から大文字に変換するプログラムを例にやってみようと思います

  • アノテーションを定義
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyFilter{}
  • MyFilterアノテーションをメソッドに付与
public class CamelToSnake {

    // CamelケースからSnakeケースに変換
    @MyFilter
    public String replace(String str) {
        return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, str);
    }
}
public class Capitalize {

    // 大文字に変換
    @MyFilter
    public String replace(String str) {
        return str.toUpperCase();
    }
}
  • リフレクションを使って呼び出す
public class TutorialMyAnnotation {

    private static class Filter {
        Object object;
        Method method;

        Filter(Object object, Method method) {
            this.object = object;
            this.method = method;
        }
    }

    private List<Filter> filters = new ArrayList<>();

    public TutorialMyAnnotation(List<String> classNames) throws Exception {
        for (String className : classNames) {
            // リフレクションでクラス名を探す
            Class clazz = Class.forName(className);
            for (Method method : clazz.getMethods()) {
                // MyFilterアノテーションを探す
                if (method.isAnnotationPresent(MyFilter.class)) {
                    filters.add(new Filter(clazz.newInstance(), method));
                }
            }
        }
    }

    public void print(String input) throws Exception {
        String output = input;
        for (Filter filter : filters) {
            output = (String) filter.method.invoke(filter.object, output);
        }
        System.out.println(output);
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        List<String> classNames = Arrays.asList(
                "CamelToSnake", "Capitalize");

        TutorialMyAnnotation tutorialMyAnnotation = new TutorialMyAnnotation(classNames);
        tutorialMyAnnotation.print("replaceString");
}
  • 実行結果
REPLACE_STRING

まとめ

アノテーションを自分で定義することができました
リフレクションについてもどこかでまとめたいなと思います

技術Java

Posted by hosochin