アノテーション付きのJavaコードを記述する方法

メタデータ(他のデータを説明するデータ)をクラス、メソッド、および/または他のアプリケーション要素に関連付ける必要がある状況に遭遇した可能性があります。たとえば、プログラミングチームは、大規模なアプリケーションで未完成のクラスを特定する必要がある場合があります。未完了のクラスごとに、メタデータには、クラスの終了を担当する開発者の名前と、クラスの予想される完了日が含まれる可能性があります。

Java 5以前は、コメントは、メタデータをアプリケーション要素に関連付けるためにJavaが提供しなければならなかった唯一の柔軟なメカニズムでした。ただし、コメントは適切な選択ではありません。コンパイラーはそれらを無視するため、実行時にコメントは使用できません。そして、それらが利用可能であったとしても、重要なデータ項目を取得するためにテキストを解析する必要があります。データ項目の指定方法を標準化しないと、これらのデータ項目を解析できない可能性があります。

ダウンロードコードを入手するこのJava101チュートリアルの例のソースコードをダウンロードします。ジェフフリーゼンによって作成されました。

非標準の注釈メカニズム

Javaは、メタデータをアプリケーション要素に関連付けるための非標準のメカニズムを提供します。たとえば、transient予約語を使用すると、シリアル化中に除外されるフィールドに注釈付ける(データを関連付ける)ことができます。

Java 5は、メタデータをさまざまなアプリケーション要素に関連付けるための標準メカニズムであるアノテーションを導入することで、すべてを変えました。このメカニズムは、次の4つのコンポーネントで構成されています。

  • @interface注釈タイプを宣言するためのメカニズム。
  • メタ注釈タイプ。注釈タイプが適用されるアプリケーション要素を識別するために使用できます。注釈(注釈タイプのインスタンス)の存続期間を識別するため。もっと。
  • プログラムのランタイムアノテーションを検出するために使用できるJavaReflection APIの拡張機能(今後の記事で説明)を介したアノテーション処理のサポート、およびアノテーションを処理するための一般化されたツール。
  • 標準の注釈タイプ。

この記事を読み進める中で、これらのコンポーネントの使用方法を説明します。

@interfaceを使用したアノテーションタイプの宣言

@記号の直後にinterface予約語と識別子を指定することで、注釈タイプを宣言できます。たとえば、リスト1は、スレッドセーフなコードに注釈を付けるために使用できる単純な注釈タイプを宣言しています。

リスト1:ThreadSafe.java

public @interface ThreadSafe {}

このアノテーションタイプを宣言した後@、メソッドヘッダーの直後にタイプ名を付けて、スレッドセーフと見なすメソッドの前にこのタイプのインスタンスを付けます。リスト2は、main()メソッドに注釈が付けられている簡単な例を示しています@ThreadSafe

リスト2 :(AnnDemo.javaバージョン1)

public class AnnDemo {@ThreadSafe public static void main(String [] args){}}

ThreadSafeインスタンスは、アノテーションタイプ名以外のメタデータを提供しません。ただし、このタイプに要素を追加することでメタデータを提供できます。要素は、アノテーションタイプの本体に配置されたメソッドヘッダーです。

コード本体がないことに加えて、要素には次の制限があります。

  • メソッドヘッダーはパラメーターを宣言できません。
  • メソッドヘッダーはthrows句を提供できません。
  • 方法ヘッダーの戻り型がプリミティブ型でなければならない(例えば、int)、 、java.lang.Stringjava.lang.Class列挙、注釈型、またはこれらのいずれかのタイプのアレイ。戻り値の型に他の型を指定することはできません。

別の例として、リスト3は、ToDo特定のコーディングジョブを識別し、ジョブが終了する日付を指定し、ジョブの完了を担当するコーダーに名前を付ける3つの要素を持つ注釈タイプを示しています。

リスト3 :(ToDo.javaバージョン1)

public @interface ToDo {int id(); 文字列finishDate(); String coder()デフォルト "n / a"; }

各要素はパラメータまたはthrows句を宣言せず、有効な戻り値の型(intまたはString)を持ち、セミコロンで終了することに注意してください。また、最後の要素は、デフォルトの戻り値を指定できることを示しています。この値は、注釈が要素に値を割り当てない場合に返されます。

リスト4はToDo、未完成のクラスメソッドに注釈を付けるために使用しています。

リスト4 :(AnnDemo.javaバージョン2)

public class AnnDemo {public static void main(String [] args){String [] citys = {"New York"、 "Melbourne"、 "Beijing"、 "Moscow"、 "Paris"、 "London"}; sort(cities); } @ToDo(id = 1000、finishDate = "10/10/2019"、coder = "John Doe")static void sort(Object [] objects){}}

リスト4は、メタデータ項目を各要素に割り当てています。たとえば、1000に割り当てられidます。とは異なりcoderidandfinishDate要素を指定する必要があります。それ以外の場合、コンパイラはエラーを報告します。にcoder値が割り当てられていない場合は、デフォルト"n/a"値が想定されます。

Javaは、String value()メタデータ項目のコンマ区切りリストを返すために使用できる特別な要素を提供します。リスト5は、のリファクタリングされたバージョンでこの要素を示していますToDo

リスト5 :(ToDo.javaバージョン2)

public @interface ToDo {String value(); }

ときはvalue()注釈型の唯一の要素である、あなたは指定する必要はありませんvalueし、=この要素に文字列を割り当てるときに、代入演算子。リスト6は、両方のアプローチを示しています。

リスト6 :(AnnDemo.javaバージョン3)

public class AnnDemo {public static void main(String [] args){String [] citys = {"New York"、 "Melbourne"、 "Beijing"、 "Moscow"、 "Paris"、 "London"}; sort(cities); } @ToDo(value = "1000,10 / 10/2019、John Doe")static void sort(Object [] objects){} @ToDo( "1000,10 / 10/2019、John Doe")static boolean search( Object []オブジェクト、オブジェクトキー){falseを返します; }}

メタアノテーションタイプの使用—柔軟性の問題

タイプ(クラスなど)、メソッド、ローカル変数などに注釈を付けることができます。ただし、この柔軟性には問題があります。たとえば、ToDoメソッドのみに制限したいが、リスト7に示すように、他のアプリケーション要素に注釈を付けるためにメソッドを使用することを妨げるものは何もありません。

リスト7 :(AnnDemo.javaバージョン4)

@ToDo("1000,10/10/2019,John Doe") public class AnnDemo { public static void main(String[] args) { @ToDo(value = "1000,10/10/2019,John Doe") String[] cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(value = "1000,10/10/2019,John Doe") static void sort(Object[] objects) { } @ToDo("1000,10/10/2019,John Doe") static boolean search(Object[] objects, Object key) { return false; } }

In Listing 7, ToDo is also used to annotate the AnnDemo class and cities local variable. The presence of these erroneous annotations might confuse someone reviewing your code, or even your own annotation processing tools. For the times when you need to narrow an annotation type’s flexibility, Java offers the Target annotation type in its java.lang.annotation package.

Target is a meta-annotation type — an annotation type whose annotations annotate annotation types, as opposed to a non-meta-annotation type whose annotations annotate application elements, such as classes and methods. It identifies the kinds of application elements to which an annotation type is applicable. These elements are identified by Target’s ElementValue[] value() element.

java.lang.annotation.ElementType is an enum whose constants describe application elements. For example, CONSTRUCTOR applies to constructors and PARAMETER applies to parameters. Listing 8 refactors Listing 5’s ToDo annotation type to restrict it to methods only.

Listing 8:ToDo.java (version 3)

import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface ToDo { String value(); }

Given the refactored ToDo annotation type, an attempt to compile Listing 7 now results in the following error message:

AnnDemo.java:1: error: annotation type not applicable to this kind of declaration @ToDo("1000,10/10/2019,John Doe") ^ AnnDemo.java:6: error: annotation type not applicable to this kind of declaration @ToDo(value="1000,10/10/2019,John Doe") ^ 2 errors

Additional meta-annotation types

Java 5 introduced three additional meta-annotation types, which are found in the java.lang.annotation package:

  • Retention indicates how long annotations with the annotated type are to be retained. This type’s associated java.lang.annotation.RetentionPolicy enum declares constants CLASS (compiler records annotations in class file; virtual machine doesn’t retain them to save memory — default policy), RUNTIME (compiler records annotations in class file; virtual machine retains them), and SOURCE (compiler discards annotations).
  • Documented indicates that instances of Documented-annotated annotations are to be documented by javadoc and similar tools.
  • Inherited indicates that an annotation type is automatically inherited.

Java 8 introduced the java.lang.annotation.Repeatable meta-annotation type. Repeatable is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. In other words, you can apply multiple annotations from the same repeatable annotation type to an application element, as demonstrated here:

@ToDo(value = "1000,10/10/2019,John Doe") @ToDo(value = "1001,10/10/2019,Kate Doe") static void sort(Object[] objects) { }

This example assumes that ToDo has been annotated with the Repeatable annotation type.

Processing annotations

Annotations are meant to be processed; otherwise, there’s no point in having them. Java 5 extended the Reflection API to help you create your own annotation processing tools. For example, Class declares an Annotation[] getAnnotations() method that returns an array of java.lang.Annotation instances describing annotations present on the element described by the Class object.

Listing 9 presents a simple application that loads a class file, interrogates its methods for ToDo annotations, and outputs the components of each found annotation.

Listing 9:AnnProcDemo.java

import java.lang.reflect.Method; public class AnnProcDemo { public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("usage: java AnnProcDemo classfile"); return; } Method[] methods = Class.forName(args[0]).getMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(ToDo.class)) { ToDo todo = methods[i].getAnnotation(ToDo.class); String[] components = todo.value().split(","); System.out.printf("ID = %s%n", components[0]); System.out.printf("Finish date = %s%n", components[1]); System.out.printf("Coder = %s%n%n", components[2]); } } } }

After verifying that exactly one command-line argument (identifying a class file) has been specified, main() loads the class file via Class.forName(), invokes getMethods() to return an array of java.lang.reflect.Method objects identifying all public methods in the class file, and processes these methods.

Method processing begins by invoking Method’s boolean isAnnotationPresent(Class annotationClass) method to determine if the annotation described by ToDo.class is present on the method. If so, Method’s T getAnnotation(Class annotationClass) method is called to obtain the annotation.

The ToDo annotations that are processed are those whose types declare a single String value() element (see Listing 5). Because this element’s string-based metadata is comma-separated, it needs to be split into an array of component values. Each of the three component values is then accessed and output.

Compile this source code (javac AnnProcDemo.java). Before you can run the application, you’ll need a suitable class file with @ToDo annotations on its public methods. For example, you could modify Listing 6’s AnnDemo source code to include public in its sort() and search() method headers. You’ll also need Listing 10’s ToDo annotation type, which requires the RUNTIME retention policy.

Listing 10:ToDo.java (version 4)

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 ToDo { String value(); }

Compile the modified AnnDemo.java and Listing 10, and execute the following command to process AnnDemo’s ToDo annotations:

java AnnProcDemo AnnDemo

If all goes well, you should observe the following output:

ID = 1000 Finish date = 10/10/2019 Coder = John Doe ID = 1000 Finish date = 10/10/2019 Coder = John Doe

Processing annotations with apt and the Java compiler

Java 5では、apt一般的な方法で注釈を処理するためのツールが導入されました。Java 6はaptの機能をjavacコンパイラツールに移行し、Java 7は非推奨aptになりましたが、その後削除されました(Java 8以降)。

標準の注釈タイプ

および、Java 5の導入、と。これらの3つの注釈タイプは、コンパイラコンテキストでのみ使用されるように設計されているため、保持ポリシーはに設定されています。TargetRetentionDocumentedInheritedjava.lang.Deprecatedjava.lang.Overridejava.lang.SuppressWarningsSOURCE

非推奨