Javaの道(19)--注釈(構文、事前定義注釈、メタ注釈、繰返し注釈、注釈と反射)

10754 ワード

前言
公式注釈の定義は次のとおりです.
注釈(メタデータ形式)は、プログラム自体に属さないプログラムに関するデータを提供します.注釈は、それらの注釈のコードの操作に直接影響しません.
注記には、次のような多くの用途があります.
  • コンパイラの情報-コンパイラは、注釈を使用してエラーを検出したり、警告を抑制したりすることができます.
  • コンパイル時および導入時処理-ソフトウェアツールは、注釈情報を処理してコード、XMLファイルなどを生成することができる.
  • ランタイム処理-実行時に注釈を確認できます.

  • 初めて見た時に「このニマは何か」と思ったので、簡単な言い方に変えました.
    注釈を「ラベル」と見なすことができますこの「ラベル」には、属性の評価(注釈要素)が含まれています.プログラム要素にこの「ラベル」(注釈を使用)を貼ることができます.貼ることができる以上、自然に何らかの方法(反射)でこの「ラベル」を引き裂くことができます(注釈を取得します).
    このような比喩があれば、以下の注釈の説明がもっと理解しやすいと信じています.注解が何なのか理解できないと思ったら、「注解≒ラベル」を思い出すといいでしょう.
    1.基礎知識
    1.1注記のフォーマット
  • 注記に要素がない:
    @Entity
    
    //  ,      @Override   
    @Override
    void myMethod(){...}
    
  • 注記には複数の要素があり、これらの要素には値があります:
    //    @Author          
    @Author(
       name = "Benjamin Franklin",
       date = "3/27/2003"
    )
    class MyClass() { ... }
    
  • 注記に1つの要素しかない場合:
    //@SuppressWarnings      Java       
    
    @SuppressWarnings(value = "unchecked")
    void myMethod() { ... }
    
    //       value   
    @SuppressWarnings("unchecked")
    void myMethod() { ... }
    
  • 同じ宣言に複数の注記を追加できます:
    @Author(name = "Jane Doe")
    @EBook
    class MyClass { ... }
    
  • は、反復注釈(同じ種類の注釈を用いる)
    @Author(name = "Jane Doe")
    @Author(name = "John Smith")
    class MyClass { ... }
    
  • を用いることができる.
    1.2注記を使用できる場所
    注記は、次の宣言に適用できます.
  • クラス
  • フィールド
  • 方法
  • その他のプログラム要素
  • Java SE 8以前は、注釈は宣言にのみ適用されていました.Java SE 8からタイプ注記が追加されました.具体例は以下の通りです.
  • クラスインスタンス
    new @Interned MyObject();
    
  • を作成
  • 型変換
    myString = (@NonNull String) str;
    
  • implements句
    class UnmodifiableList implements @Readonly List { ... }
    
  • 異常放出文
    void monitorTemperature() throws @Critical TemperatureException { ... }
    
  • 2.注釈タイプの宣言
    注記タイプ定義は、@interfaceキーワードを使用して定義されるインタフェース定義と同様です.
    @interface MyAnnotation{
        
    }
    

    前述したように、注釈には次のように定義された要素があります.
    @interface MyAnnotation{
        int id() default 0;
        String msg();
        //...
    }
    

    このように定義すると、次の例のように、このタイプの注釈を使用して値を入力できます.
    @MyAnnotation(id = 3, msg = "hello annotation")
    public class Test {
        //...
    }
    

    @MyAnnotation注記を定義するときに、デフォルト値を指定するためのキーワードdefaultを使用しました.デフォルト値を指定すると、注釈を使用して値を入力しない場合、Javaは自動的にデフォルト値に設定します.次のように呼び出すことができます.
    @MyAnnotation(msg = "hello annotation")
    public class Test {
        //...
    }
    

    注記要素のタイプには制限があります.注記要素は次のタイプしか使用できません.
  • 基本データ型
  • String
  • Class
  • enum
  • Annotation
  • 以上のタイプの配列
  • 3.事前定義された注記
    3.1 Javaで使用される事前定義された注記
    Javaで使用される注釈のタイプは次のとおりです.
  • @Deprecatedは、注記された要素が破棄されたことを示し、これ以上使用すべきではありません.プログラムが@Deprecated注記付きのメソッド/クラス/フィールドを使用する限り、コンパイラは警告を生成します.使用を推奨しない場合は、Javadoc@deprecatedを使用してタグを付けます.
       // Javadoc comment follows
        /**
         * @deprecated
         * explanation of why it was deprecated
         */
        @Deprecated
        static void deprecatedMethod() { }
    
  • @Overrideこの注釈通知コンパイラが注釈された要素は、スーパークラスで宣言された要素を上書きします.具体的には、書き換え方法の場合に使用されます.
       //               
       @Override 
       int overriddenMethod() { }
    

    実際には、書き換え方法に@Overrideは必須ではありません.この注釈を使用するのは、エラーを防ぐためです.
    @Override注記を使用したメソッドがスーパークラスメソッドを正しく上書きしていない場合、コンパイラはエラーを報告します.
  • @SuppressWarningsこの注記は、コンパイラが生成可能な特定の警告を禁止することを通知します.たとえば、次の例では、通常コンパイラが警告を生成する有効な方法を使用しますが、@SuppressWarnings注釈を使用すると、警告の生成が禁止されます.
    @SuppressWarnings("deprecation")
        void useDeprecatedMethod() {
            // deprecation warning
            // - suppressed
            objectOne.deprecatedMethod();
        }
    

    コンパイラ警告について、Java言語仕様では、deprecationuncheckedの2つのカテゴリが指定されています.deprecation:廃棄方法を使用した場合のカテゴリunchecked:汎用型が出現する前に記述されたレガシーコードと対話する場合に発生する可能性のある警告カテゴリ.
  • @SafeVarargsは、注釈がメソッドまたはコンストラクション関数に適用されると、可変パラメータに対して安全でない動作を実行しないことを保証します.この注釈を使用すると、可変パラメータに関するunchecked警告の生成が禁止されます.
  • @FunctionalInterfaceはJava SE 8に導入され、タイプ宣言の目的は関数インタフェース注釈として指摘されている.

  • 3.2元注記
    他の注釈に適用される注釈をメタ注釈と呼ぶ.
  • @Retentionコメントの格納方法を指定します.
  • RetentionPolicy.SOURCE-タグの注記はソースレベルにのみ保持され、コンパイラによって無視されます.
  • RetentionPolicy.CLASS-タグ付けされた注記はコンパイラによってコンパイラによって保持されますが、Java仮想マシン(JVM)は無視されます.
  • RetentionPolicy.RUNTIME-マークされた注記はJVMによって保持されるため、ランタイム環境で使用できます.

  • @Documentedの役割は、注釈の要素をJavadocに含めることができる
  • です.
  • @Targetは、注釈を適用できるJava要素のタイプを制限します.
  • ElementType.ANNOTATION_TYPEは、注釈タイプに適用することができる.
  • ElementType.CONSTRUCTORは、構造関数に適用することができる.
  • ElementType.FIELDは、フィールドまたは属性に適用することができる.
  • ElementType.LOCAL_VARIABLEは、局所変数に適用することができる.
  • ElementType.METHODは、方法レベルの注釈に適用することができる.
  • ElementType.PACKAGEは、パケット宣言に適用することができる.
  • ElementType.PARAMETERは、方法のパラメータに適用することができる.
  • ElementType.TYPEは、クラスの任意の要素に適用することができる.

  • @Inheritedは、注釈タイプがスーパークラスから継承できることを示しています.スーパークラスが「@Inheritedで注記された」注釈を使用すると、そのサブクラスに注釈が追加されていない場合、サブクラスはスーパークラスの注釈を継承します.比較して、例を挙げると:
    @Inherited
    @interface Test {}
    
    
    @Test
    public class A {}
    
    
    public class B extends A {}
    
    の上で、@Test注釈は継承することができて、Aは@Test注釈を使って、BはAを継承して、しかもいかなる注釈を追加していないで、そこでBも@Testという注釈を持っています.
  • @Repeatable Java SE 8に加えられた特性は,注釈された注釈が同じ宣言に複数回適用できることを示している.詳細については、「注記の繰り返し」セクションを参照してください.

  • 4.繰り返し注記
    Java SE 8には、繰り返し注釈が加えられており、宣言やタイプ参照に同じ注釈を適用することができます.
    宣言には2つのステップがあります.
  • 繰り返し可能な注釈タイプ
  • を宣言
  • 宣言に含まれる注釈タイプ
  • 次の例では、重複注釈の宣言と使用方法を説明します.
    ステップ1:繰り返し可能な注釈タイプを宣言する
    注記タイプには@Repeatableメタ注記を使用してマークする必要があります.
    @Repeatable(Schedules.class)
    public @interface Schedule {
      String dayOfMonth() default "first";
      String dayOfWeek() default "Mon";
      int hour() default 12;
    }
    

    ここで,@Repeatableメタ注釈の値は,Javaコンパイラが繰り返し注釈を格納するために生成したコンテナ注釈のタイプである.この例では、注記タイプがSchedulesであるため、@Schedule注記は@Schedules注記に格納されます.
    ステップ2:注釈の種類を宣言
    含まれる注記タイプには、配列タイプのvalue要素が必要です.配列のタイプには、繰り返し可能な注記タイプが必要です.
    public @interface Schedules {
        Schedule[] value();
    }
    

    5.検索(Retrieving)注記-注記と反射
    前に説明したのは、注釈をどのように宣言し、使用するかです.では、ここでは、注釈を取得する方法について説明します.
    これは主に反射によって実現され、主に以下のAnnotatedElementインタフェースが提供する抽象的な方法に関する.
  • 注釈が適用されたかどうかを問い合わせる
    default boolean isAnnotationPresent(Class extends Annotation> annotationClass)
    
  • 注釈の取得
    方法
    機能
    説明 T getAnnotation(Class annotationClass)
    プログラム要素に存在する指定したタイプの注釈を返し、そのタイプの注釈が存在しない場合nullを返します.
    汎用パラメータは、注釈タイプまたは注釈タイプのサブクラスしか言えないことを示します.Annotation[] getAnnotations()
    継承された注釈を含む要素に存在するすべての注釈を返します.注記がない場合は、長さがゼロの配列を返します.default T[] getAnnotationsByType(Class annotationClass)
    Java SE 8は、プログラム要素を修飾する、指定されたタイプの複数の注釈を得るための新しい方法を提供する.default T getDeclaredAnnotation(Class annotationClass)
    プログラム要素を直接修飾し、指定したタイプの注記を返します.このタイプの注釈が存在しない場合はnullを返します.
    継承された注記を無視default T[] getDeclaredAnnotationsByType(Class annotationClass)
    Java SE 8は、プログラム要素を直接修飾する、指定されたタイプの複数の注釈を得るための新しい方法を追加した.
    継承された注記を無視Annotation[] getDeclaredAnnotations()
    この要素に直接存在するすべての注記を返します.注記がない場合は、長さがゼロの配列を返します.
    継承された注記を無視

  • 具体的に例を示します.
    MyAnnotation.java
    @Documented
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)//       JVM  ,      
    @Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})//        、      
    public @interface MyAnnotation{ 
        String type() default "ignore";
        String[] hobby();
    }
    
    

    TestAnnotation.java
    @MyAnnotation(type = "class",hobby = {"sleep","play"})
    public class TestAnnotation {
        
        @MyAnnotation(type = "Field",hobby = {"read"})
        private String whdalive;
        
        @MyAnnotation(type = "Field",hobby = {"piano"})
        private String cy;
        
        @MyAnnotation(type = "Method",hobby = {"guitar"})
        public void method1(){
            
        }
        
        public static void main(String[] args) {
            
            //       
            Class clz = TestAnnotation.class;
            //            
            boolean clzHasAnno = clz.isAnnotationPresent(MyAnnotation.class);
            //   ,        
            if(clzHasAnno) {
                MyAnnotation annotation = clz.getAnnotation(MyAnnotation.class);
                String type = annotation.type();
                String[] hobby = annotation.hobby();
                System.out.println(clz.getName() + ", type = " + type + ", hobby = " + Arrays.asList(hobby).toString());
            }
            //      
            Field[] fields = clz.getDeclaredFields();
            for(Field field : fields) {
                boolean fieldHasAnno = field.isAnnotationPresent(MyAnnotation.class);
                if (fieldHasAnno) {
                    MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
                    String type = annotation.type();
                    String[] hobby = annotation.hobby();
                    System.out.println(field.getName() + ", type = " + type + ", hobby = " + Arrays.asList(hobby).toString());
                }
            }
            //      
            Method[] methods = clz.getDeclaredMethods();
            for (Method method : methods) {
                boolean methodHasAnno = method.isAnnotationPresent(MyAnnotation.class);
                if (methodHasAnno) {
                    MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                    String type = annotation.type();
                    String[] hobby = annotation.hobby();
                    System.out.println(method.getName() + ", type = " + type + ", hobby = " + Arrays.asList(hobby).toString());
                }
                
            }
        }
    }
    

    出力結果
    com.whdalive.reflection.TestAnnotation, type = class, hobby = [sleep, play]
    whdalive, type = Field, hobby = [read]
    cy, type = Field, hobby = [piano]
    method1, type = Method, hobby = [guitar]
    

    まとめ
    注記はJavaが導入した非常に人気のあるメカニズムであり、構造化され、タイプチェック能力を持つ新しい方法を提供します.コードが乱雑で読みにくいことを招くことなく、コードにメタデータを加えることができます.
    同時に、注釈はAndroidの多くのオープンソースフレームワークの実現の基礎でもある.例えばButterKnife、Retrofit、Dagger 2など.
    注釈はやはりよく勉強する必要がある.
    共に励ます.