Java 註解徹底解析

關於註解首先引入官方文檔的一句話:Java 註解用於爲 Java 代碼提供元數據。做爲元數據,註解不直接影響你的代碼執行,但也有一些類型的註解實際上能夠用於這一目的。Java 註解是從 Java5 開始添加到 Java 的。看完這句話也許你仍是一臉懵逼,接下我將從註解的定義、元註解、註解屬性、自定義註解、註解解析JDK 提供的註解這幾個方面再次瞭解註解(Annotation)git

註解的定義

  • 平常開發中新建Java類,咱們使用class、interface比較多,而註解和它們同樣,也是一種類的類型,他是用的修飾符爲 @interface

Java中新建類

註解類的寫法

  • 咱們新建一個註解MyTestAnnotation
public @interface MyTestAnnotation {

}
複製代碼
  • 接着咱們就能夠在類或者方法上做用咱們剛剛新建的註解
@MyTestAnnotation
public class test {
   @MyTestAnnotation
   public static void main(String[] args){
   }
}
複製代碼
  • 以上咱們只是瞭解了註解的寫法,可是咱們定義的註解中還沒寫任何代碼,如今這個註解毫無心義,要如何使註解工做呢?接下來咱們接着瞭解元註解。

元註解

  • 元註解顧名思義咱們能夠理解爲註解的註解,它是做用在註解中,方便咱們使用註解實現想要的功能。元註解分別有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五種。

@Retention

  • Retention英文意思有保留、保持的意思,它表示註解存在階段是保留在源碼(編譯期),字節碼(類加載)或者運行期(JVM中運行)。在@Retention註解中使用枚舉RetentionPolicy來表示註解保留時期
  • @Retention(RetentionPolicy.SOURCE),註解僅存在於源碼中,在class字節碼文件中不包含
  • @Retention(RetentionPolicy.CLASS), 默認的保留策略,註解會在class字節碼文件中存在,但運行時沒法得到
  • @Retention(RetentionPolicy.RUNTIME), 註解會在class字節碼文件中存在,在運行時能夠經過反射獲取到
  • 若是咱們是自定義註解,則經過前面分析,咱們自定義註解若是隻存着源碼中或者字節碼文件中就沒法發揮做用,而在運行期間能獲取到註解才能實現咱們目的,因此自定義註解中確定是使用 @Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTestAnnotation {

}
複製代碼

@Target

  • Target的英文意思是目標,這也很容易理解,使用@Target元註解表示咱們的註解做用的範圍就比較具體了,能夠是類,方法,方法參數變量等,一樣也是經過枚舉類ElementType表達做用類型
  • @Target(ElementType.TYPE) 做用接口、類、枚舉、註解
  • @Target(ElementType.FIELD) 做用屬性字段、枚舉的常量
  • @Target(ElementType.METHOD) 做用方法
  • @Target(ElementType.PARAMETER) 做用方法參數
  • @Target(ElementType.CONSTRUCTOR) 做用構造函數
  • @Target(ElementType.LOCAL_VARIABLE)做用局部變量
  • @Target(ElementType.ANNOTATION_TYPE)做用於註解(@Retention註解中就使用該屬性)
  • @Target(ElementType.PACKAGE) 做用於包
  • @Target(ElementType.TYPE_PARAMETER) 做用於類型泛型,即泛型方法、泛型類、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) 類型使用.能夠用於標註任意類型除了 class (jdk1.8加入)
  • 通常比較經常使用的是ElementType.TYPE類型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {

}
複製代碼

@Documented

  • Document的英文意思是文檔。它的做用是可以將註解中的元素包含到 Javadoc 中去。

@Inherited

  • Inherited的英文意思是繼承,可是這個繼承和咱們平時理解的繼承大同小異,一個被@Inherited註解了的註解修飾了一個父類,若是他的子類沒有被其餘註解修飾,則它的子類也繼承了父類的註解。
  • 下面咱們來看個@Inherited註解例子
/**自定義註解*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
}
/**父類標註自定義註解*/
@MyTestAnnotation
public class Father {
}
/**子類*/
public class Son extends Father {
}
/**測試子類獲取父類自定義註解*/
public class test {
   public static void main(String[] args){

      //獲取Son的class對象
       Class<Son> sonClass = Son.class;
      // 獲取Son類上的註解MyTestAnnotation能夠執行成功
      MyTestAnnotation annotation = sonClass.getAnnotation(MyTestAnnotation.class);
   }
}
複製代碼

@Repeatable

  • Repeatable的英文意思是可重複的。顧名思義說明被這個元註解修飾的註解能夠同時做用一個對象屢次,可是每次做用註解又能夠表明不一樣的含義。
  • 下面咱們看一我的玩遊戲的例子
/**一我的喜歡玩遊戲,他喜歡玩英雄聯盟,絕地求生,極品飛車,塵埃4等,則咱們須要定義一我的的註解,他屬性表明喜歡玩遊戲集合,一個遊戲註解,遊戲屬性表明遊戲名稱*/
/**玩家註解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface People {
    Game[] value() ;
}
/**遊戲註解*/
@Repeatable(People.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Game {
    String value() default "";
}
/**玩遊戲類*/
@Game(value = "LOL")
@Game(value = "PUBG")
@Game(value = "NFS")
@Game(value = "Dirt4")
public class PlayGame {
}
複製代碼
  • 經過上面的例子,你可能會有一個疑問,遊戲註解中括號的變量是啥,其實這和遊戲註解中定義的屬性對應。接下來咱們繼續學習註解的屬性。

註解的屬性

  • 經過上一小節@Repeatable註解的例子,咱們說到註解的屬性。註解的屬性其實和類中定義的變量有殊途同歸之處,只是註解中的變量都是成員變量(屬性),而且註解中是沒有方法的,只有成員變量,變量名就是使用註解括號中對應的參數名,變量返回值註解括號中對應參數類型。相信這會你應該會對上面的例子有一個更深的認識。而@Repeatable註解中的變量則類型則是對應Annotation(接口)的泛型Class。
/**註解Repeatable源碼*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}
複製代碼

註解的本質

  • 註解的本質就是一個Annotation接口
/**Annotation接口源碼*/
public interface Annotation {
   
    boolean equals(Object obj);

    int hashCode();
    
    Class<? extends Annotation> annotationType();
}
複製代碼
  • 經過以上源碼,咱們知道註解自己就是Annotation接口的子接口,也就是說註解中實際上是能夠有屬性和方法,可是接口中的屬性都是static final的,對於註解來講沒什麼意義,而咱們定義接口的方法就至關於註解的屬性,也就對應了前面說的爲何註解只有屬性成員變量,其實他就是接口的方法,這就是爲何成員變量會有括號,不一樣於接口咱們能夠在註解的括號中給成員變量賦值。

註解屬性類型

  • 註解屬性類型能夠有如下列出的類型
  • 1.基本數據類型
  • 2.String
  • 3.枚舉類型
  • 4.註解類型
  • 5.Class類型
  • 6.以上類型的一維數組類型

註解成員變量賦值

  • 若是註解又多個屬性,則能夠在註解括號中用「,」號隔開分別給對應的屬性賦值,以下例子,註解在父類中賦值屬性
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
    String name() default "mao";
    int age() default 18;
}

@MyTestAnnotation(name = "father",age = 50)
public class Father {
}
複製代碼

獲取註解屬性

  • 前面咱們說了不少註解如何定義,放在哪,如今咱們能夠開始學習註解屬性的提取了,這纔是使用註解的關鍵,獲取屬性的值纔是使用註解的目的。
  • 若是獲取註解屬性,固然是反射啦,主要有三個基本的方法
/**是否存在對應 Annotation 對象*/
  public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }
  
 /**獲取 Annotation 對象*/
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }
 /**獲取全部 Annotation 對象數組*/   
 public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }    
複製代碼
  • 下面結合前面的例子,咱們來獲取一下註解屬性,在獲取以前咱們自定義的註解必須使用元註解@Retention(RetentionPolicy.RUNTIME)
public class test {
   public static void main(String[] args) throws NoSuchMethodException {

        /**
         * 獲取類註解屬性
         */
        Class<Father> fatherClass = Father.class;
        boolean annotationPresent = fatherClass.isAnnotationPresent(MyTestAnnotation.class);
        if(annotationPresent){
            MyTestAnnotation annotation = fatherClass.getAnnotation(MyTestAnnotation.class);
            System.out.println(annotation.name());
            System.out.println(annotation.age());
        }

        /**
         * 獲取方法註解屬性
         */
        try {
            Field age = fatherClass.getDeclaredField("age");
            boolean annotationPresent1 = age.isAnnotationPresent(Age.class);
            if(annotationPresent1){
                Age annotation = age.getAnnotation(Age.class);
                System.out.println(annotation.value());
            }

            Method play = PlayGame.class.getDeclaredMethod("play");
            if (play!=null){
                People annotation2 = play.getAnnotation(People.class);
                Game[] value = annotation2.value();
                for (Game game : value) {
                    System.out.println(game.value());
                }
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:github

獲取註解屬性運行結果

JDK 提供的註解

註解 做用 注意事項
@Override 它是用來描述當前方法是一個重寫的方法,在編譯階段對方法進行檢查 jdk1.5中它只能描述繼承中的重寫,jdk1.6中它能夠描述接口實現的重寫,也能描述類的繼承的重寫
@Deprecated 它是用於描述當前方法是一個過期的方法
@SuppressWarnings 對程序中的警告去除。

註解做用與應用

  • 如今咱們再次回頭看看開頭官方文檔的那句描述

Java 註解用於爲 Java 代碼提供元數據。做爲元數據,註解不直接影響你的代碼執行,但也有一些類型的註解實際上能夠用於這一目的。數組

  • 通過咱們前面的瞭解,註解實際上是個很方便的東西,它存活的時間,做用的區域均可以由你方便設置,只是你用註解來幹嗎的問題

使用註解進行參數配置

  • 下面咱們看一個銀行轉帳的例子,假設銀行有個轉帳業務,轉帳的限額可能會根據匯率的變化而變化,咱們能夠利用註解靈活配置轉帳的限額,而不用每次都去修改咱們的業務代碼。
/**定義限額註解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BankTransferMoney {
    double maxMoney() default 10000;
}
/**轉帳處理業務類*/
public class BankService {
    /**
     * @param money 轉帳金額
     */
    @BankTransferMoney(maxMoney = 15000)
    public static void TransferMoney(double money){
        System.out.println(processAnnotationMoney(money));

    }
    private static String processAnnotationMoney(double money) {
        try {
            Method transferMoney = BankService.class.getDeclaredMethod("TransferMoney",double.class);
            boolean annotationPresent = transferMoney.isAnnotationPresent(BankTransferMoney.class);
            if(annotationPresent){
                BankTransferMoney annotation = transferMoney.getAnnotation(BankTransferMoney.class);
                double l = annotation.maxMoney();
                if(money>l){
                   return "轉帳金額大於限額,轉帳失敗";
                }else {
                    return"轉帳金額爲:"+money+",轉帳成功";
                }
            }
        } catch ( NoSuchMethodException e) {
            e.printStackTrace();
        }
        return "轉帳處理失敗";
    }
    public static void main(String[] args){
        TransferMoney(10000);
    }
}
複製代碼

運行結果:bash

轉帳處理運行結果

  • 經過上面的例子,只要匯率變化,咱們就改變註解的配置值就能夠直接改變當前最大限額。

第三方框架的應用

  • 做爲一個Android 開發者,日常咱們所使用的第三方框架ButterKnife,Retrofit2,Dagger2等都有註解的應用,若是咱們要了解這些框架的原理,則註解的基礎知識則是必不可少的。

註解的做用

  • 提供信息給編譯器: 編譯器能夠利用註解來檢測出錯誤或者警告信息,打印出日誌。
  • 編譯階段時的處理: 軟件工具能夠用來利用註解信息來自動生成代碼、文檔或者作其它相應的自動處理。
  • 運行時處理: 某些註解能夠在程序運行的時候接受代碼的提取,自動作相應的操做。
  • 正如官方文檔的那句話所說,註解可以提供元數據,轉帳例子中處理獲取註解值的過程是咱們開發者直接寫的註解提取邏輯,處理提取和處理 Annotation 的代碼統稱爲 APT(Annotation Processing Tool)。上面轉帳例子中的processAnnotationMoney方法就能夠理解爲APT工具類。

最後說點

到此,對於Java中註解的解析就結束了。最後,也很是感謝您閱讀個人文章,文章中若是有錯誤,請你們給我提出來,你們一塊兒學習進步,若是以爲個人文章給予你幫助,也請給我一個喜歡和關注,同時也歡迎訪問個人我的博客框架

相關文章
相關標籤/搜索