Java註解(Annotation)詳解

幻海流心發表於2018-05-10

Java註解(Annotation)詳解

1.Annotation的概念

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

  • 註解是一種可以新增到Java原始碼的後設資料.
  • 類,方法,變數,引數,包都可以被註解.
  • 註解對註解的程式碼並沒有直接的影響.
  • 註解僅僅是個標記.註解之所以起作用是對其解析後做了相應的處理

2.Annotation分類

  • 標準Annotation
    • 標準Annotation是指Java內建的三個Annnotaion:
    • @Override:用於修飾此方法覆蓋了父類的方法.
    • @Deprecated:用於修飾已經過時的方法.
    • @SuppressWarnnings:用於通知java編譯器禁止特定的編譯警告.
  • 元Annotation(註解的註解)
    • 元Annotation是用來定義Annotation的Annotation
    • 元Annotation可以定義Annotation的作用範圍,使用在什麼元素上等
    • 元註解共有四種@Retention, @Target, @Inherited, @Documented
  • 自定義Annotation

3.元Annotation

  • @Retention:注在其他的註解A上,用來說明A的保留範圍,可選值 SOURCE(原始碼時),CLASS(編譯時),RUNTIME(執行時),預設為 CLASS
    • SOURCE:A只保留在原始碼中,A會被編譯期忽略.(原始碼可用)
    • CLASS:A會通過編譯儲存在CLASS檔案中,但會被JVM在執行時忽略,執行時不可見.(原始碼+CLASS可用)
    • RUNTIME:A會被JVM獲取,並在執行時通過反射獲取.(原始碼+CLASS+執行時均可用)
  • @Target:注在其他的註解A上,用來限制A可用修飾那些程式元素.未標註Target表示無限制,可修飾所有元素.
    • ANNOTATION_TYPE: A可以應用到其他註解上
    • CONSTRUCTOR: A可以使用到構造器上
    • FIELD: A可以使用到域或屬性上
    • LOCAL_VARIABLE: A可以使用到區域性變數上。
    • METHOD: A可以使用到方法上。
    • PACKAGE: A可以使用到包宣告上。
    • PARAMETER: A可以使用到方法的引數上
    • TYPE: A可以使用到類,介面(包括註解),或列舉的宣告上
  • @Inherited:預設情況下,父類的註解不會被子類繼承.
    • Inherited注在其他的註解A上.
    • 只有當A是註解在類Class上面,Inherited才會起作用,其他任何情況下無效果.
    • 當A註解在類C上面,則C的所有子孫類,都會繼承應用A註解;
  • @Documented:注在其他的註解A上,A將會作為Javadoc產生的文件中的內容。註解都預設不會成為成為文件中的內容。

4.自定義Annotation

  1. 建立自定義Annotation流程

    • public @interface 自定義註解名稱
      public @interface CustomAnnotation{***}
          
      複製程式碼
    • 設定自定義Annotation的保留範圍和目標,Retention和Target是最重要的兩個元Anitation.
      @Retention( RetentionPolicy.RUNTIME )
      @Target( ElementType.TYPE )
      public @interface CustomAnnotation{***}
      複製程式碼
    • 設定自定義Annotation的註解引數(註解成員)
      • 註解引數支援的資料型別
        • 所有基本資料型別(int,float,boolean,byte,double,char,long,short)
        • String型別
        • Class型別
        • enum型別
        • Annotation型別
        • 以上所有型別的一維陣列
      • 註解引數宣告方式
        @Retention( RetentionPolicy.RUNTIME )
        @Target( ElementType.TYPE )
        public @interface CustomAnnotation{
            //註解引數型別可以是1-6中任一種,包括列舉
            public enum Skill{JAVA,ANDROID,IOS}
            Skill mySkill() default Skill.ANDROID;
            String attr1();
            //可以使用default設定預設值
            int attr2() default 100;
            //修飾符只能用public
            public boolean attr3() default false;
        }
        @Retention( RetentionPolicy.RUNTIME )
        @Target( ElementType.TYPE )
        public @interface CustomAnnotation{
            //只有一個註解引數,使用value()
            String value();
        }
        複製程式碼
        • 自定義Annotation的引數型別必須滿足上一條1到6中的範圍.
        • 自定義Annotation的引數訪問方法只能是public,或不寫.
        • 自定義Annotation的引數可以加 default 設定預設值.
        • 自定義Annotation若只有1個引數,使用value().
  2. 自定義Annotation的註解引數的預設值

    註解元素必須有確定的值,要麼在定義註解的預設值中指定,要麼在使用註解時指定,非基本型別的註解元素的值不可為null。因此, 使用空字串或0作為預設值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因為每個註解的宣告中,所有元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字串或者負數,一次表示某個元素不存在,在定義註解時,這已經成為一個習慣用法。

    示例:
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AnotherAnnotation{
        String author() default "";
        int age() default -1;
    }
    複製程式碼
  3. 使用剛剛建立的自定義註解

    @CustomAnnotation(attr1 = "屬性1", attr2 = 90, attr3 = true)
    public class AnnotationTestClass{
        ***
    }
    複製程式碼

5.Annotation解析

  • 執行時 Annotation 解析

    執行時 Annotation 指 @Retention 為 RUNTIME 的 Annotation

    • Class,Method,Field中都有以下3個方法可以呼叫
      • public T getAnnotation(Class annotationClass) 按照傳入的引數獲取指定型別的註解。返回null說明當前元素不帶有此註解。
      • public final boolean isAnnotationPresent(Class<? extends Annotation> annotationType) 檢查傳入的註解是否存在於當前元素。
      • public Annotation[] getAnnotations() 返回該元素的所有註解,包括沒有顯式定義該元素上的註解。
    • 執行時 Annotation 解析示例
      public void testCustomAnnotation() {
          try {
              Class cls = Class.forName("com.jet.annotation.AnnotationTestClass");
              CustomAnnotation customAnnotation = (CustomAnnotation)cls.getAnnotation(CustomAnnotation.class);
              System.out.println("customAnnotation mySkill:" + cus.mySkill());
              System.out.println("customAnnotation attr1:" + cus.attr1());
              System.out.println("customAnnotation attr2:" + cus.attr2());
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          }
      }
      複製程式碼
  • 編譯時 Annotation 解析

    編譯時 Annotation 指 @Retention 為 CLASS 的 Annotation,甴編譯器自動解析


6.編譯時Annotation解析

編譯時Annotation解析 相對複雜,下面單獨進行分析

首先申明:下面內容僅僅討論 編譯時Annotation的解析

  1. 編譯時Annotation的解析,是由Annotation Processor完成
  2. Annotation Processor(註解處理器)
    • 註解處理器是一個在javac中的,用來在編譯時掃描和處理註解的工具
    • 我們可以為特定的註解,註冊自定義的註解處理器
    • 在編譯期間,JVM會自動執行註冊過的註解處理器
    • 一個註解的Annotation Processor,以Java程式碼(或者編譯過的class)為輸入,生成.java檔案作為輸出.這意味著我們可以生成新的Java程式碼!這些生成的Java程式碼是在生成的.java檔案中,新生成的.java檔案會和普通的手動編寫的Java原始碼一樣被javac編譯
  3. 每一個註解處理器都是繼承於AbstractProcessor,需要關注的有以下4個方法
public abstract class AbstractProcessor implements Processor {

    //對一些工具進行初始化
    public synchronized void init(ProcessingEnvironment processingEnv)
    
    //你在這裡定義你的註解處理器註冊到哪些註解上,必須指定;
    //它的返回值是一個字串的集合,包含本處理器想要處理的註解型別的合法全稱
    public Set<String> getSupportedAnnotationTypes()
    
    //指定該註解處理器使用的JAVA版本,通常返回SourceVersion.latestSupported()
    public SourceVersion getSupportedSourceVersion()
    
    //真正生成java程式碼的地方
    //annotations:請求處理的註解型別集合
    //roundEnv:可以讓你查詢出包含特定註解的被註解元素,相當於“有關全域性原始碼的上下文環境”
    //如果返回 true,則這些註解已宣告並且不要求後續 Processor 處理它們;
    //如果返回 false,則這些註解未宣告並且可能要求後續 Processor 處理它們
    public abstract boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv)
    
}
複製程式碼
  1. 自定義註解處理器,就是繼承AbstractProcessor並重寫上述4個方法

關於編譯時Annotation解析,這裡推薦一篇文章【Android】註解框架(三)-- 編譯時註解,手寫ButterKnife,按照文章上面流程敲一遍程式碼,相信可以對自定義註解的建立及解析有一個深入的瞭解!

7.註解對App的影響

  1. 執行時註解對效能有影響,編譯時註解對App的效能沒有影響.
  2. 執行時註解的解析完全依賴於反射,反射的效率比直接呼叫慢,只有過多使用執行時註解時才對效率有一定影響
  3. java檔案編譯成.class檔案。再對class檔案進行打包等一系列處理。生成apk。最終才執行到我們的手機上。而編譯時註解,就是在java編譯生成.class檔案這一步進行的操作。根本和我們的apk執行無關,不存在影響效能的問題;
  4. 編譯時註解庫,在app的引用一般如下:
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    //API模組:android library
    implementation project(':butterknife')
    //Annotation模組:java library
    implementation project(':butterknife-annotations')
    //Annotation解析模組:java library
    annotationProcessor project(':butterknife-compiler')
}
複製程式碼

實際在打包生成APK的過程中,只有 API模組和Annotation模組 會被打包進APK,Annotation解析模組是提供給IDE使用的,在我們APK中並不存在.

相關文章