Java Language
注釈
サーチ…
前書き
Javaでは、 注釈はJavaソースコードに追加できる構文メタデータの一形式です。プログラム自体に含まれていないプログラムに関するデータを提供します 。注釈は注釈を付けるコードの操作に直接影響を与えません。クラス、メソッド、変数、パラメータ、パッケージに注釈を付けることができます。
構文
- @AnnotationName // 'マーカーアノテーション'(パラメータなし)
- @AnnotationName(someValue)// 'value'という名前のパラメータを設定する
- @AnnotationName(param1 = value1)//名前付きパラメータ
- @AnnotationName(param1 = value1、param2 = value2)//複数の名前付きパラメータ
- @AnnotationName(param1 = {1、2、3})//名前付き配列パラメータ
- @AnnotationName({value1})//名前が 'value'のパラメータとして単一要素を持つ配列
備考
ビルトイン注釈
JavaのStandard Editionには、いくつかのアノテーションがあらかじめ定義されています。自分で定義する必要はなく、すぐに使用できます。コンパイラはメソッド、クラス、コードの基本的なチェックを可能にします。
@オーバーライド
この注釈はメソッドに適用され、このメソッドはスーパークラスのメソッドをオーバーライドするか、抽象スーパークラスのメソッド定義を実装する必要があると言います。このアノテーションが他の種類のメソッドで使用されている場合、コンパイラはエラーをスローします。
コンクリートスーパークラス
public class Vehicle {
public void drive() {
System.out.println("I am driving");
}
}
class Car extends Vehicle {
// Fine
@Override
public void drive() {
System.out.prinln("Brrrm, brrm");
}
}
抽象クラス
abstract class Animal {
public abstract void makeNoise();
}
class Dog extends Animal {
// Fine
@Override
public void makeNoise() {
System.out.prinln("Woof");
}
}
動作しません
class Logger1 {
public void log(String logString) {
System.out.prinln(logString);
}
}
class Logger2 {
// This will throw compile-time error. Logger2 is not a subclass of Logger1.
// log method is not overriding anything
@Override
public void log(String logString) {
System.out.println("Log 2" + logString);
}
}
主な目的は、メソッドをオーバーライドしていると思われるミスタイプをキャッチすることですが、実際には新しいメソッドを定義しています。
class Vehicle {
public void drive() {
System.out.println("I am driving");
}
}
class Car extends Vehicle {
// Compiler error. "dirve" is not the correct method name to override.
@Override
public void dirve() {
System.out.prinln("Brrrm, brrm");
}
}
@Override
の意味は時間とともに変化していることに注意してください。
- Java 5では、注釈付きメソッドはスーパークラスチェーンで宣言された非抽象メソッドをオーバーライドする必要がありました。
- Java 6以降では、注釈付きメソッドがクラススーパークラス/インタフェース階層で宣言された抽象メソッドを実装している場合にも、満足しています。
(これはコードをJava 5にバックポーティングするときに問題を引き起こすことがあります。)
@Deprecated
これはメソッドを非推奨とマークします。これにはいくつかの理由があります。
APIに欠陥があり、修正するのは実際的ではありません。
APIの使用はエラーにつながる可能性がありますが、
APIは別のAPIに取って代わられましたが、
APIは時代遅れですが、
APIは実験的なものであり、互換性のない変更を受けることがあります。
または上記の任意の組み合わせを含む。
非推奨の具体的な理由は、通常、APIのドキュメントに記載されています。
アノテーションを使用すると、コンパイラはエラーを発生させます。 IDEは、このメソッドを何らかの形で廃止されたものとして強調表示するかもしれません
class ComplexAlgorithm {
@Deprecated
public void oldSlowUnthreadSafeMethod() {
// stuff here
}
public void quickThreadSafeMethod() {
// client code should use this instead
}
}
@SuppressWarnings
ほとんどすべての場合、コンパイラが警告を発するとき、最も適切なアクションは原因を修正することです。いくつかの例では(ジェネリックスコードは非安全な事前生成コードを使用しています)、これは可能ではないかもしれません。予期していて修正できない警告を抑制して、予期せぬ警告をより明確に表示できます。
このアノテーションは、クラス、メソッド、または行全体に適用できます。警告のカテゴリをパラメータとして取ります。
@SuppressWarnings("deprecation")
public class RiddledWithWarnings {
// several methods calling deprecated code here
}
@SuppressWarning("finally")
public boolean checkData() {
// method calling return from within finally block
}
予期しない警告も抑制されないように、アノテーションのスコープを可能な限り制限する方がよいでしょう。たとえば、アノテーションのスコープを1行に限定します。
ComplexAlgorithm algorithm = new ComplexAlgorithm();
@SuppressWarnings("deprecation") algoritm.slowUnthreadSafeMethod();
// we marked this method deprecated in an example above
@SuppressWarnings("unsafe") List<Integer> list = getUntypeSafeList();
// old library returns, non-generic List containing only integers
このアノテーションでサポートされている警告は、コンパイラごとに異なる場合があります。 JLSでは、 unchecked
deprecation
ていunchecked
警告とdeprecation
警告のみが具体的に言及されています。認識されない警告の種類は無視されます。
@SafeVarargs
型消去のため、 void method(T... t)
はvoid method(Object[] t)
変換され、コンパイラは常にvarargsの使用がタイプセーフであることを確認できないことを意味します。例えば:
private static <T> void generatesVarargsWarning(T... lists) {
使用が安全である場合があります。この場合、 SafeVarargs
アノテーションを使用してSafeVarargs
注釈を付けて警告を抑制することができます。これはあなたの使用が安全でない場合でも警告を隠すことは明らかです。
@FunctionalInterface
これは、FunctionalInterfaceをマークするために使用されるオプションの注釈です。 FunctionalInterface仕様に準拠していない場合(単一の抽象メソッドを持つ)、コンパイラがエラーを発生させます。
@FunctionalInterface
public interface ITrade {
public boolean check(Trade t);
}
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
リフレクションによるランタイムアノテーションチェック
JavaのReflection APIを使用すると、プログラマは実行時にクラスフィールド、メソッド、およびアノテーションに対してさまざまなチェックと操作を実行できます。ただし、実行時に注釈がすべて表示されるようにするには、以下の例に示すように、 RetentionPolicy
をRUNTIME
に変更する必要があります。
@interface MyDefaultAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyRuntimeVisibleAnnotation {
}
public class AnnotationAtRuntimeTest {
@MyDefaultAnnotation
static class RuntimeCheck1 {
}
@MyRuntimeVisibleAnnotation
static class RuntimeCheck2 {
}
public static void main(String[] args) {
Annotation[] annotationsByType = RuntimeCheck1.class.getAnnotations();
Annotation[] annotationsByType2 = RuntimeCheck2.class.getAnnotations();
System.out.println("default retention: " + Arrays.toString(annotationsByType));
System.out.println("runtime retention: " + Arrays.toString(annotationsByType2));
}
}
注釈タイプの定義
注釈タイプは@interface
定義されます。パラメータは、通常のインタフェースのメソッドと同様に定義されます。
@interface MyAnnotation {
String param1();
boolean param2();
int[] param3(); // array parameter
}
デフォルト値
@interface MyAnnotation {
String param1() default "someValue";
boolean param2() default true;
int[] param3() default {};
}
メタ注釈
メタ注釈は、注釈タイプに適用できる注釈です。特別な事前定義メタアノテーションは、アノテーションタイプの使用方法を定義します。
@ターゲット
@Target
メタアノテーションは、アノテーションを適用できるタイプを制限します。
@Target(ElementType.METHOD)
@interface MyAnnotation {
// this annotation can only be applied to methods
}
@Target({ElementType.FIELD, ElementType.TYPE})
配列表記を使用して複数の値を追加できます
利用可能な値
ElementType | ターゲット | ターゲット要素の使用例 |
---|---|---|
ANNOTATION_TYPE | アノテーションタイプ | |
コンストラクタ | コンストラクタ | |
フィールド | フィールド、列挙定数 | |
LOCAL_VARIABLE | メソッド内の変数宣言 | |
パッケージ | package(in package-info.java ) | |
方法 | メソッド | |
パラメータ | メソッド/コンストラクタのパラメータ | |
タイプ | クラス、インタフェース、列挙型 | |
ElementType | ターゲット | ターゲット要素の使用例 |
---|---|---|
TYPE_PARAMETER | タイプパラメータ宣言 | |
TYPE_USE | タイプの使用 | |
@保持
@Retention
メタアノテーションは、アプリケーションコンパイルプロセスまたは実行中のアノテーション可視性を定義します。デフォルトでは、アノテーションは.class
ファイルに含まれていますが、実行時には表示されません。実行時にアノテーションにアクセスできるようにするには、そのアノテーションにRetentionPolicy.RUNTIME
を設定する必要があります。
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
// this annotation can be accessed with reflections at runtime
}
利用可能な値
RetentionPolicy | 効果 |
---|---|
クラス | アノテーションは.class ファイルで利用できますが、実行時には使用できません |
ランタイム | アノテーションは実行時に利用可能で、リフレクションを介してアクセスできます |
ソース | アノテーションはコンパイル時に使用できますが、 .class ファイルには追加されません。注釈は、例えば、注釈プロセッサによって使用することができる。 |
@Documented
@Documented
メタアノテーションは、 javadocのようなAPIドキュメントジェネレータによって使用法が文書化されるべきアノテーションをマークするために使用されます。値はありません。 @Documented
を使用すると、アノテーションを使用するすべてのクラスが生成されたドキュメントページにそのアノテーションをリストします。 @Documented
を指定しないと、ドキュメンテーション内の注釈を使用するクラスを見ることはできません。
@継承されました
@Inherited
メタアノテーションは、クラスに適用されるアノテーションに関連しています。値はありません。アノテーションを@Inherited
としてマークすると、アノテーションクエリが動作する方法が変更されます。
- 継承されていない注釈の場合、照会は検査されるクラスのみを検査します。
- 継承されたアノテーションについては、アノテーションのインスタンスが見つかるまでスーパークラスチェーンも(再帰的に)チェックされます。
スーパークラスのみが照会されることに注意してください。クラス階層のインターフェースにアタッチされた注釈は無視されます。
@繰り返し可能
@Repeatable
メタアノテーションはJava 8で追加されました。アノテーションの複数のインスタンスがアノテーションのターゲットにアタッチできることを示します。このメタ注釈には値はありません。
実行時に注釈値を取得する
Reflectionを使用してAnnotationが適用されているメソッドまたはフィールドまたはクラスをフェッチし、目的のプロパティをフェッチすることで、Annotationの現在のプロパティを取得できます。
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String key() default "foo";
String value() default "bar";
}
class AnnotationExample {
// Put the Annotation on the method, but leave the defaults
@MyAnnotation
public void testDefaults() throws Exception {
// Using reflection, get the public method "testDefaults", which is this method with no args
Method method = AnnotationExample.class.getMethod("testDefaults", null);
// Fetch the Annotation that is of type MyAnnotation from the Method
MyAnnotation annotation = (MyAnnotation)method.getAnnotation(MyAnnotation.class);
// Print out the settings of the Annotation
print(annotation);
}
//Put the Annotation on the method, but override the settings
@MyAnnotation(key="baz", value="buzz")
public void testValues() throws Exception {
// Using reflection, get the public method "testValues", which is this method with no args
Method method = AnnotationExample.class.getMethod("testValues", null);
// Fetch the Annotation that is of type MyAnnotation from the Method
MyAnnotation annotation = (MyAnnotation)method.getAnnotation(MyAnnotation.class);
// Print out the settings of the Annotation
print(annotation);
}
public void print(MyAnnotation annotation) {
// Fetch the MyAnnotation 'key' & 'value' properties, and print them out
System.out.println(annotation.key() + " = " + annotation.value());
}
public static void main(String[] args) {
AnnotationExample example = new AnnotationExample();
try {
example.testDefaults();
example.testValues();
} catch( Exception e ) {
// Shouldn't throw any Exceptions
System.err.println("Exception [" + e.getClass().getName() + "] - " + e.getMessage());
e.printStackTrace(System.err);
}
}
}
出力は次のようになります。
foo = bar
baz = buzz
注釈を繰り返す
Java 8までは、同じアノテーションの2つのインスタンスを1つの要素に適用できませんでした。標準的な回避策は、他のアノテーションの配列を保持するコンテナアノテーションを使用することでした。
// Author.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
String value();
}
// Authors.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Authors {
Author[] value();
}
// Test.java
@Authors({
@Author("Mary"),
@Author("Sam")
})
public class Test {
public static void main(String[] args) {
Author[] authors = Test.class.getAnnotation(Authors.class).value();
for (Author author : authors) {
System.out.println(author.value());
// Output:
// Mary
// Sam
}
}
}
Java 8では、 @Repeatable
アノテーションを使用して、コンテナアノテーションをよりクリーンで透明な方法で使用@Repeatable
ます。まずこれをAuthor
クラスに追加します:
@Repeatable(Authors.class)
これは、複数の@Author
アノテーションを@Authors
コンテナで囲まれているかのように扱うようJavaに指示します。また、 Class.getAnnotationsByType()
を使用して、 @Author
配列にそのコンテナではなく独自のクラスでアクセスすることもできます。
@Author("Mary")
@Author("Sam")
public class Test {
public static void main(String[] args) {
Author[] authors = Test.class.getAnnotationsByType(Author.class);
for (Author author : authors) {
System.out.println(author.value());
// Output:
// Mary
// Sam
}
}
}
継承された注釈
デフォルトでは、クラスアノテーションはそれらを拡張する型には適用されません。これは、 @Inherited
アノテーションをアノテーション定義に追加することで変更できます
例
次の2つのアノテーションを考えてみましょう。
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotationType {
}
そして
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UninheritedAnnotationType {
}
3つのクラスに次のように注釈が付けられているとします。
@UninheritedAnnotationType
class A {
}
@InheritedAnnotationType
class B extends A {
}
class C extends B {
}
このコードを実行する
System.out.println(new A().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println(new B().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println(new C().getClass().getAnnotation(InheritedAnnotationType.class));
System.out.println("_________________________________");
System.out.println(new A().getClass().getAnnotation(UninheritedAnnotationType.class));
System.out.println(new B().getClass().getAnnotation(UninheritedAnnotationType.class));
System.out.println(new C().getClass().getAnnotation(UninheritedAnnotationType.class));
アノテーションのパッケージに応じて、これに似た結果が出力されます。
null
@InheritedAnnotationType()
@InheritedAnnotationType()
_________________________________
@UninheritedAnnotationType()
null
null
注釈は、インタフェースではなくクラスから継承できることに注意してください。
アノテーション・プロセッサを使用したコンパイル時間処理
この例は、注釈付き要素のコンパイル時間チェックを行う方法を示しています。
アノテーション
@Setter
アノテーションは、メソッドにマーカを適用することができます。注釈は、編集中に破棄され、その後は使用できなくなります。
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Setter {
}
アノテーションプロセッサ
SetterProcessor
クラスは、アノテーションを処理するためにコンパイラによって使用されます。 @Setter
アノテーションで注釈を付けられたメソッドがpublic
で、 set
で始まり大文字が4文字目の名前の非static
メソッドであるかどうかをチェックします。これらの条件の1つが満たされない場合、エラーがMessager
書き込まれます。コンパイラはこれをstderrに書き込みますが、他のツールではこの情報を別々に使用できます。たとえばNetBeans IDEでは、エディタにエラーメッセージを表示するために使用されるアノテーションプロセッサをユーザーが指定できます。
package annotation.processor;
import annotation.Setter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes({"annotation.Setter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {
private Messager messager;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// get elements annotated with the @Setter annotation
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Setter.class);
for (Element element : annotatedElements) {
if (element.getKind() == ElementKind.METHOD) {
// only handle methods as targets
checkMethod((ExecutableElement) element);
}
}
// don't claim annotations to allow other processors to process them
return false;
}
private void checkMethod(ExecutableElement method) {
// check for valid name
String name = method.getSimpleName().toString();
if (!name.startsWith("set")) {
printError(method, "setter name must start with \"set\"");
} else if (name.length() == 3) {
printError(method, "the method name must contain more than just \"set\"");
} else if (Character.isLowerCase(name.charAt(3))) {
if (method.getParameters().size() != 1) {
printError(method, "character following \"set\" must be upper case");
}
}
// check, if setter is public
if (!method.getModifiers().contains(Modifier.PUBLIC)) {
printError(method, "setter must be public");
}
// check, if method is static
if (method.getModifiers().contains(Modifier.STATIC)) {
printError(method, "setter must not be static");
}
}
private void printError(Element element, String message) {
messager.printMessage(Diagnostic.Kind.ERROR, message, element);
}
@Override
public void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// get messager for printing errors
messager = processingEnvironment.getMessager();
}
}
パッケージング
コンパイラによって適用されるには、注釈プロセッサをSPIで使用可能にする必要があります( ServiceLoaderを参照)。
これを行うには、 META-INF/services/javax.annotation.processing.Processor
というテキストファイルを、他のファイルに加えてアノテーションプロセッサと注釈を含むjarファイルに追加する必要があります。ファイルには注釈プロセッサの完全修飾名を含める必要があります。つまり、次のようになります
annotation.processor.SetterProcessor
以下では、jarファイルがAnnotationProcessor.jar
と呼ばれるものと仮定します。
アノテーション付きクラスの例
次のクラスは、保持ポリシーに従って正しい要素にアノテーションが適用されている、デフォルトパッケージのサンプルクラスです。しかしながら、注釈プロセッサのみが第2の方法を有効な注釈ターゲットとみなす。
import annotation.Setter;
public class AnnotationProcessorTest {
@Setter
private void setValue(String value) {}
@Setter
public void setString(String value) {}
@Setter
public static void main(String[] args) {}
}
javacでアノテーションプロセッサを使用する
注釈プロセッサがSPIを使用して検出された場合、注釈プロセッサは注釈付き要素を処理するために自動的に使用されます。例えばAnnotationProcessorTest
クラスをコンパイルする
javac -cp AnnotationProcessor.jar AnnotationProcessorTest.java
次の出力が得られます
AnnotationProcessorTest.java:6: error: setter must be public
private void setValue(String value) {}
^
AnnotationProcessorTest.java:12: error: setter name must start with "set"
public static void main(String[] args) {}
^
2 errors
正常にコンパイルする代わりに。 .class
ファイルは作成されません。
これは、 javac
-proc:none
オプションを指定することで防ぐことができます。代わりに、 -proc:only
指定することで、通常のコンパイルを-proc:only
することもできます。
IDE統合
ネットビーンズ
注釈プロセッサは、NetBeansエディタで使用できます。これを行うには、注釈プロセッサをプロジェクト設定で指定する必要があります。
Project Properties
>Build
>Compiling
Enable Annotation Processing
をEnable Annotation Processing
をEnable Annotation Processing in Editor
ためのチェックマークをEnable Annotation Processing in Editor
追加するアノテーションプロセッサリストの横にある[
Add
]Add
クリックします表示されるポップアップで、注釈プロセッサの完全修飾クラス名を入力し、[
Ok
]をクリックします。
結果
注釈の背後にあるアイデア
Java言語仕様では、注釈について次のように説明しています。
アノテーションは、情報をプログラム構成に関連付けるマーカーですが、実行時には影響しません。
注釈は、型または宣言の前に現れます。型や宣言の両方に適用できる場所に表示することは可能です。
注釈が正確に適用されるものは、「メタ注釈」 @Target
によって管理されます。詳細は、 「アノテーション型の定義」を参照してください。
注釈はさまざまな目的で使用されます。 SpringやSpring-MVCのようなフレームワークでは、アノテーションを使用して、依存関係を注入する場所や要求をルーティングする場所を定義します。
他のフレームワークでは、アノテーションをコード生成に使用しています。 LombokとJPAはJava(およびSQL)コードを生成するアノテーションを使用する主要な例です。
このトピックでは、次の項目について包括的に説明します。
独自の注釈を定義する方法
Java言語が提供する注釈は何ですか?
実際に注釈はどのように使用されますか?
「this」とレシーバパラメータのアノテーション
Javaアノテーションが初めて導入されたとき、インスタンスメソッドのターゲットに注釈を付けるための規定や、内部クラスコンストラクタの隠しコンストラクタパラメータはありませんでした。これは、 レシーバー・パラメーター宣言を追加してJava 8で修正されました。 JLS 8.4.1を参照してください。
receiverパラメータは、インスタンスメソッドまたは内部クラスのコンストラクタのためのオプションの構文的デバイスです。インスタンスメソッドの場合、receiverパラメーターは、メソッドが呼び出されるオブジェクトを表します。内部クラスのコンストラクタの場合、receiverパラメータは新しく構築されたオブジェクトのすぐに囲まれたインスタンスを表します。どちらの場合でも、レシーバパラメータは、表現されたオブジェクトの型をソースコード内に示すことができるだけであり、その型に注釈を付けることができる。受信側パラメータは仮パラメータではありません。より正確には、変数の宣言ではなく(4.12.3)、メソッド呼び出し式や修飾されたクラスインスタンス作成式で引数として渡される値に決して束縛されず、実行時間。
次の例は、両方の種類のレシーバパラメータの構文を示しています。
public class Outer {
public class Inner {
public Inner (Outer this) {
// ...
}
public void doIt(Inner this) {
// ...
}
}
}
レシーバパラメータの唯一の目的は、アノテーションを追加できるようにすることです。たとえば、メソッドの呼び出し時にCloseable
オブジェクトが閉じられていないことを宣言することを目的とするカスタム注釈@IsOpen
があるとします。例えば:
public class MyResource extends Closeable {
public void update(@IsOpen MyResource this, int value) {
// ...
}
public void close() {
// ...
}
}
1つのレベルでは、 @IsOpen
上の注釈this
単にドキュメントとして役立ち得ます。しかし、我々は潜在的にもっと多くを行うことができます。例えば:
- 注釈プロセッサは、ことのランタイムチェックを挿入することができ
this
閉状態でないupdate
呼ばれています。 - コードチェッカーは、静的コード分析を実行して、
update
が呼び出されたときにthis
を閉じることができるケースを見つけることができます 。
複数の注釈値を追加する
Annotationパラメータは、配列として定義されている場合は複数の値を受け入れることができます。例えば、標準アノテーション@SuppressWarnings
は次のように定義されます。
public @interface SuppressWarnings {
String[] value();
}
value
パラメータは文字列の配列です。配列初期化子に似た表記法を使用して、複数の値を設定できます。
@SuppressWarnings({"unused"})
@SuppressWarnings({"unused", "javadoc"})
単一の値だけを設定する必要がある場合は、大括弧を省略することができます。
@SuppressWarnings("unused")