アノテーション付きの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.String
、java.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
ます。とは異なりcoder
、id
andfinishDate
要素を指定する必要があります。それ以外の場合、コンパイラはエラーを報告します。に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 associatedjava.lang.annotation.RetentionPolicy
enum declares constantsCLASS
(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), andSOURCE
(compiler discards annotations).Documented
indicates that instances ofDocumented
-annotated annotations are to be documented byjavadoc
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つの注釈タイプは、コンパイラコンテキストでのみ使用されるように設計されているため、保持ポリシーはに設定されています。Target
Retention
Documented
Inherited
java.lang.Deprecated
java.lang.Override
java.lang.SuppressWarnings
SOURCE