Android 註解系列之Annotation(二)

AndyJennifer發表於2018-10-15

註解基本概念

註解(也稱為後設資料),為我們在程式碼中新增資訊提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便的使用這些資料。其中註解是引入到JAVA SE5的重要的語言變化之一。其可以提供用來完整的描述程式所需的資訊,而這些資訊是無法用Java表達的。因此,註解使得我們能夠以將由編譯器來測試和驗證的格式,儲存有關程式的額外資訊。註解可以用來生成描述符檔案。甚至是新的類定義,並且有助於減輕編寫樣板程式碼的負擔。通過使用註解。我們可以將這些後設資料儲存在Java原始碼中,並利用annotation API為自己的註解構造處理工具

註解的宣告

說了這麼多,我們先來看看註解的宣告。具體參看下面的例子:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE,PACKAGE})
public @interface HelloAnnotation {
}

複製程式碼

觀看上述例子,我們發現註解的宣告其實有點類似於Java介面的宣告。除了@符號以外,@HelloAnnotation 定義更像是一個空的介面。事實上,它與其他任何Java介面一樣。註解也會被編譯成class檔案。在定義註解時需要一些元註解,如@Retention@Target。要知道註解的正確使用,我們需要了解元註解的使用方法與作用。

元註解

Java目前內建了5種元註解,元註解主要負責註解其他的註解。這裡分別對其進行介紹:

@Target元註解

該註解主要表示該註解可以用於什麼地方,其中可能ElementType引數為以下幾種情況:

  • TYPE:用於類、介面(包括註解型別)或enum宣告
  • FIELD:用於欄位宣告,包括enum例項
  • METHOD:用於方法宣告
  • PARAMETER:用於引數宣告
  • CONSTRUCTOR:用於建構函式宣告
  • LOCAL_VARIABLE:用於區域性變數宣告
  • ANNOTATION_TYPE:用於註解可也用於註解宣告(應用於另一個註解上)
  • PACKAGE:用於包宣告
  • TYPE_PARAMETER:用於類上泛型引數的說明(JDK1.8加入
  • TYPE_USE:用於任何型別的宣告(JDK1.8加入

這裡為了方便大家理解,會對以上@Target作用範圍進行介紹,其中關於TYPE_PARAMETER與TYPE_USE會單獨著重介紹。下面我們還是以@HelloAnnotation 註解為例,其中該註解宣告如下:

@Target(value = {TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE,PACKAGE})
public @interface HelloAnnotation {
}
複製程式碼

那麼在實際程式碼中,我們可以通過@HelloAnnotation 註解定義的Target去宣告想宣告的東西。具體程式碼如下所示:

// TYPE:用於類、介面(`包括註解型別`)或enum宣告
@HelloAnnotation
class AnnotationDemo {

    //CONSTRUCTOR:用於建構函式宣告
    @HelloAnnotation
    public AnnotationDemo(String name) {
        this.name = name;
    }

    //FIELD:用於欄位宣告,包括enum例項
    @HelloAnnotation
    private String name;

    //METHOD:用於方法宣告
    @HelloAnnotation
    public void sayHello() {
        System.out.println("hello every one");
    }

    //PARAMETER:用於引數宣告
    public void saySometing(@HelloAnnotation String text) { }
    
    //LOCAL_VARIABLE:用於區域性變數宣告
    public int add(int a, int b) {
        @HelloAnnotation int total = 0;
        return a + b;
    }
    
    //ANNOTATION_TYPE:用於註解可也用於註解宣告(應用於另一個註解上)
    @HelloAnnotation
    @interface AnnotationTwo {

    }
}
複製程式碼

其中對包進行註解修飾,需要在當前包下建立package-info.java檔案,而該檔案的作用有以下三點:

  • 為標註在包上Annotation提供便利
  • 宣告包的私有類和常量
  • 提供包的整體註釋說明( 如果是專案是分“包”開發,也就是說一個包實現一個業務邏輯或功能點、或模組、或元件,則需要對一個包有很好的說明,說明這個包是幹啥的,有啥作用,版本變遷,特別說明等等)
/**
 * 主要是為了測試包的註解
 */
@HelloAnnotation
package annotation;
複製程式碼
新增的TYPE_PARAMETER與TYPE_USE

在JDK1.8中增加了TYPE_PARAMETER與TYPE_USE,其中TYPE_PARAMETER用於修飾類上的泛型引數

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {TYPE_PARAMETER})
public @interface HelloAnnotation {
}
class A<@HelloAnnotation T> {}
複製程式碼

TYPE_USE用於任何型別宣告(也包含修飾類上的泛型引數),具體情況如下所示:

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {TYPE_USE})
public @interface HelloAnnotation {
}

@HelloAnnotation
class AnnotationDemo<@HelloAnnotation T> {

    @HelloAnnotation
    public AnnotationDemo(String name) {
        this.name = name;
    }

    @HelloAnnotation
    private String name;

    public void saySometing(@HelloAnnotation String text) { }

    public int add(int a, int b) {
        @HelloAnnotation int total = 0;
        return a + b;
    }

    @HelloAnnotation
    @interface AnnotationTwo { }
}
複製程式碼
@Retention元註解

該註解表該註解在什麼級別下被儲存,可選的RetentionPolicy引數為:

  • SOURCE:該型別的註解只會保留在原始碼裡,經過編譯器編譯後,生成的class檔案裡是不會存在該註解資訊的。
  • CLASS:註解在class檔案中可用,但是會被VM丟棄(該型別的註解資訊會被保留在原始碼與class檔案中,在執行的時候,不會被載入到虛擬機器中)。注意:當註解未定義未定義Retention值時,預設值是CLASS級別
  • RUNTIME:VM將在執行期間也保留註解,因此可以通過反射機制讀取到註解的資訊(該型別的註解資訊會被保留在原始碼、class檔案和虛擬機器執行期間)。
@Documented元註解

該註解表示,是否將註解包含在JavaDoc中,具體列子如下圖所示

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
}


@HelloAnnotation
class AnnotationDemo {

    @HelloAnnotation
    public AnnotationDemo(String name) {
        this.name = name;
    }
    
    @HelloAnnotation
    private String name;

    public void saySometing(@HelloAnnotation String text) { }

    public int add(int a, int b) {
        @HelloAnnotation int total = 0;
        return a + b;
    }

}
複製程式碼

我們跳轉到專案的目錄,開啟命令列,執行javadoc -encoding utf-8 -charset utf-8 -package annotation命令(這裡我是所有的檔案都是放在annotaton包下的,所以你可以根據你自己的包名為該包下的所有.java檔案生成Doc文件)。執行完命令後我們找到自動生成的Doc文件。點選後如下圖所示:

doc文件.png

從上圖中我們可以發現,如果為註解指定了@Documented元註解,那麼在生成的Doc文件中是會有相應註解的(如圖上紅箭頭所指)。

@Inherited元註解

該註解表示,允許子類繼承父類中的註解。其實理解起來也簡單。看下面的列子:

@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Hello {
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface World {
}

@Hello
class Person {
}


@World
class Man extends Person {
    public static void main(String[] args) {
        Annotation[] annotations = Man.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.annotationType());
        }
    }
}
//輸出結果
interface annotation.Hello
interface annotation.World
複製程式碼

在上述程式碼中,建立了@Hello與@World註解,其中@Hello使用@Inherited修飾,同時我們也建立了用@Hello修飾的Person類及其用@World修飾的Man子類,然後我們通過Man.class.getAnnotations()方法獲取Man類中的註解(下文會對註解使用以及賦值進行介紹),得到上述程式碼中的輸出結果。也就證明了@Inherited元註解可以讓子類繼承父類中的註解的結論。

@Repeatable元註解(JDK 1.8之後新加入的)

該註解是JDK 1.8新加入的,該註解表示,可以在同一個地方多次使用同一種註解型別。也就是說在JDK 1.8之前是無法在同一個型別上使用相同的註解的。

JDK1.8之前

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
    String value();
}

//在JDK1.8之前是在一個地方多次使用同一種註解
@FilterPath("hello/java")
@FilterPath("hello/android")
class FileOperate {
}
複製程式碼

在上述程式碼中,我們宣告瞭@FilterPath註解,你有可能注意到了其中的String value();這段語句,這裡大家先不著急理解這段程式碼到底是什麼意思,大家就理解成給該註解提供字串賦值操作就行了(下文會對註解使用以及賦值進行介紹)。如果我們採用以上程式碼,編譯器是會報錯的。所以為了處理這種情況,在JDK1.8之前,如果想實現類似於上述相同的功能,我們一般採用下面的這種方式:


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
    String []value();
}

@FilterPath({"hello/java","hello/android"})
class FileOperate {
}
複製程式碼

將@FilterPath註解中的String value();修改為String []value();,也就是說讓該註解接受字串陣列。

JDK1.8之後

在JDK1.8之後我們可以使用@Repeatable,但是使用該註解也有一定的限制。下面我們一起來看看:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class)//新增@Repeatable元註解,注意其中的值
public @interface FilterPath {
    String value();
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPaths {
    FilterPath[] value();//註解其中的陣列型別為FilterPath
}

//現在可以在同一個地方使用同一註解啦~
@FilterPath("hello/java")
@FilterPath("hello/android")
class FileOperate {
}
複製程式碼

在上述程式碼中,我們建立了@FilterPath與@FilterPaths兩個註解,需要注意的是我們在@FilterPath註解上增加了元註解@Repeatable(FilterPaths.class)其中的引數FilterPaths.class是指明接受同一個型別上重複註解的容器,(也就是接受重複的@FilterPath註解),那麼我們再看@FilterPaths中宣告瞭 FilterPath[] value();,也就是其接受@FilterPath註解型別。

@Repeatable元註解使用注意事項

為了處理@Repeatble註解,JDK1.8在AnnotatedElement介面中提供了getAnnotationsByType與getAnnotationsByType方法,(注意:如果我們採用傳統的方法,也就是getAnnotation(Class<A> annotationClass)方法來獲取宣告的註解,我們需要傳入註解容器的class,而不是需要重複的註解的class)。這裡我們還是以JDK1.8之後中提到的程式碼為例:

    public static void main(String[] args) {

        //從該類上獲取FilterPath註解資訊
        FilterPath filterPath = FileOperate.class.getAnnotation(FilterPath.class);
        System.out.println(filterPath);

        //從該類上獲取FilterPaths註解資訊
        System.out.println("----------------------------");
        FilterPaths filterPaths = FileOperate.class.getAnnotation(FilterPaths.class);
        System.out.println(filterPaths);
        for (FilterPath path : filterPaths.value()) {
            System.out.println(path.value());
        }

        //通過getAnnotationsByType
        System.out.println("----------------------------");
        FilterPath[] annotationsByType = FileOperate.class.getAnnotationsByType(FilterPath.class);
        if (annotationsByType != null) {
            for (FilterPath path : annotationsByType) {
                System.out.println(path.value());
            }
        }

        //通過getDeclaredAnnotationsByType
        System.out.println("----------------------------");
        FilterPath[] declaredAnnotationsByType = FileOperate.class.getDeclaredAnnotationsByType(FilterPath.class);
        if (declaredAnnotationsByType != null) {
            for (FilterPath path : declaredAnnotationsByType) {
                System.out.println(path.value());
            }
        }
    }


//輸出結果
null
----------------------------
@annotation.FilterPaths(value=[@annotation.FilterPath(value=hello/java), @annotation.FilterPath(value=hello/android)])
hello/java
hello/android
----------------------------
hello/java
hello/android
----------------------------
hello/java
hello/android
複製程式碼

從輸出結果來看,我們並不能通過getAnnotation(FilterPath.class)獲取註解(獲得的註解為null),而是需要通過getAnnotation(FilterPaths.class)來獲取)。同時如果我們採getAnnotationsByType(FilterPath.class)getDeclaredAnnotationsByType(FilterPath.class)就能獲取到正確的值。這裡需要注意getAnnotationsByTypegetDeclaredAnnotationsByType方法的區別,如果子類呼叫getAnnotationsByType方法且該子類的父類中宣告瞭用@Inherited修飾的註解,那麼可以獲得父類中的註解。而getDeclaredAnnotationsByType是獲取不到父類中宣告的註解的。

註解的使用與支援屬性型別

註解支援屬性型別

在瞭解了註解的定義與元註解之後,我們一起來了解註解元素中可以定義的屬性。其中支援的具體型別如下所示:

  • 所有的基本型別(int、float、boolean)等
  • String
  • Class
  • enum
  • Annotaion
  • 以及以上型別的陣列

如果你使用了其他型別,那麼編譯器就會報錯,注意!!!!也不能使用任何型別的包裝型別。不過由於自動打包的存在,這也不算什麼限制。註解亦可以作為元素的型別。也就是說註解可以巢狀

註解新增屬性

我們已經知道了註解中屬性的支援型別,現在就開始為註解新增屬性吧。其基本語法是: 型別 屬性名();,請看如下例子:

//宣告列舉
enum Week {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}


@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
    String text();
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {

    //基本型別及其陣列型別
    int intAttr();
    float floatAttr() ;
    boolean booleanAttr() ;

    int[] intArray() ;
    float[] floatArray() ;
    boolean[] booleanArry() ;

    //String型別及其陣列型別
    String stringAttr() ;
    String[] stringArray();

    //Class型別及其陣列型別
    Class classAttr() ;
    Class[] classArray();

    //enum型別及其陣列型別
    Week day() ;
    Week[] week();

    //註解巢狀
    HelloAnnotation HelloAnnotation();
}
複製程式碼

在上圖中我將註解的支援的所有型別都展示出來了,這樣我相信大家都能非常好的理解了。

為屬性指定預設值(預設值)

屬性除了使用者指定值外,還支援預設值,其語法為:屬性 屬性名() default 預設值;,那麼結合上述的例子:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {

    //基本型別及其陣列型別
    int intAttr() default -1;

    float floatAttr() default -1f;

    boolean booleanAttr() default false;

    int[] intArray() default {1, 2, 3};

    float[] floatArray() default {1f, 2f, 3f};

    boolean[] booleanArry() default {true, false, true};

    //String型別及其陣列型別
    String stringAttr() default "";

    String[] stringArray();

    //Class型別及其陣列型別
    Class classAttr() default Class.class;

    Class[] classArray();

    //enum型別
    Week day() default Week.MONDAY;

    //enum陣列型別
    Week[] week() default {Week.MONDAY,Week.THURSDAY};

    //註解巢狀
    HelloAnnotation HelloAnnotation() default @HelloAnnotation(text = "word");
}
複製程式碼

也就是說當使用者自己沒有指定相應屬性值的時候,如果屬性設定了預設值,那麼該屬性的值就是預設值。但是設定屬性的預設值時有限制的。具體內容看下面的介紹。

註解預設值限制

雖然限制我們已經可以在註解定義我們想要的資訊,但是在Java中,註解中的元素型別必須要麼有預設值,要麼在使用註解是提供元素的值。其次對於非基本型別的元素,無論是在原始碼中宣告時,或是在註解介面中定義預設值時,都不能以null作為其值。這個約束使得處理器很難發現一個元素的存在和缺失的狀態,因為在每個註解的宣告中,所有的元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字串或負數,以此表示某個元素不存在。也就說像這樣的程式碼編譯器是不會通過的:

    String stringAttr() default null;//錯誤!!!!
    String[] stringArray() default null;//錯誤!!!
複製程式碼
value屬性

如果一個註解中有一個名稱為value的屬性,且你只想設定value屬性(即其他屬性都採用預設值或者你只有一個value屬性),那麼可以省略掉“value=”部分。具體程式碼如下:

//第一種情況,只有一個vaule屬性,那麼你在使用時候可以直接@WorldAnnotation("hello")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {
    String value();
}

//第二種情況,有多個屬性但是其屬性都有預設值,
//你只使用value屬性,那麼你在使用時候可以直接@WorldAnnotation("hello")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {
    int intAttr() default -1;
    float floatAttr() default -1f;
    String value();
}
複製程式碼

註解與反射機制

在瞭解了註解的定義與屬性的新增後,現在我們在來看看註解的實際運用情況。註解的使用需要與Java的反射機制結合使用。所以瞭解其中的瞭解兩者之前的關係尤為重要。

註解與反射機制的關係

眾所周知,JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意方法和屬性;這種動態獲取資訊以及動態呼叫物件方法的功能稱為java語言的反射機制。那麼從Java的整個類載入機制來看,過程是如下這樣:

java類載入圖.png

對於Java類的載入主要分為以下步驟:

  • 將程式中的*.java檔案通過javac命令編譯成副檔名為*.class檔案。其中*.class檔案儲存著Java程式碼轉換後的虛擬機器指令。
  • 當需要使用某個類時,JVM(Java 虛擬機器)將會載入它的*.class檔案,並建立對應的Class物件。其中Class物件中不僅有著類的宣告定義,還有 Constructor(類的構造器定義)、Field(類的成員變數定義)、Method(類的方法定義)、Package(類的包定義)。
那註解到底和類的載入有什麼關係呢?

當我們宣告瞭註解,且將註解的生命週期設定為@Retention(RetentionPolicy.RUNTIME),那麼在編譯成class檔案的時候,會將註解新增到檔案中去,那麼JVM根據class檔案生成相應的Class物件之後就會帶有註解資訊。那麼我們通過Class物件中的Constructor、Field、Method等類,就能獲取其上宣告的註解資訊了。那註解資訊到底是以宣告形式宣告與表現的呢?

這裡我們還是以@HelloAnnotation 註解為例,當我們宣告瞭註解後,通過javap命令獲取編譯後的的位元組碼資訊

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
    String value();

}
//通過javap -p HelloAnnotation.class
public interface annotation.HelloAnnotation extends java.lang.annotation.Annotation {
  public abstract java.lang.String value();
}
複製程式碼

從上圖中,我們可以得知經過編譯後,其實註解最終會繼承Annotation介面。也就是說註解最終會以java.lang.annotation.Annotation物件的形式在Class物件中進行展示或儲存。

註解的處理

為了方便處理介面資訊以及實現物件導向的規則,其中Constructor、Field、Method、Class、Package類都實現了AnnotatedElement介面。具體關係如下圖所示:

整體繼承關係圖.png

也就是最終的註解註解處理全部都交給了AnnotatedElement介面來實現。那現在我們來看看該介面的方法宣告。

AnnotatedElement中的方法宣告

AnnotatedElement介面中為我們提供了以下幾個方法來獲取註解資訊:

方法名稱 返回值 方法說明
getAnnotation(Class annotationClass) <T extends Annotation> T 返回元素上指定型別的註解,如果無,則返回為null
getAnnotations() Annotation[ ] 返回元素上存在的所有註解,包括從父類繼承的
getAnnotationsByType(Class annotationClass) since 1.8 <T extends Annotation> T [ ] 返回元素上指定的型別的註解陣列,包括父類的註解,如果無,返回長度為0的陣列,該方法與getAnnotation(Class annotationClass)的主要區別是,該方法可以檢查註解是不是重複的。如果是這樣,嘗試通過“檢視”容器註釋來找到該型別的一個或多個註釋。
getDeclaredAnnotation(Class annotationClass) <T extends Annotation> T 返回該元素上的指定型別的所有的註解,不包括父類的註解,如果無,返回長度為0的陣列
getDeclaredAnnotationsByType(Class annotationClass) since 1.8 <T extends Annotation> T [ ] 同getAnnotationsByType(Class annotationClass)方法類似,只是獲取的註解中不包括父類的註解
getDeclaredAnnotations() Annotation[ ] 返回該元素上的所有的註解,不包括父類的註解,如果無,返回長度為0的陣列

其中getAnnotationsByType(Class<T> annotationClass)getDeclaredAnnotationsByType(Class<T> annotationClass)方法是jdk 1.8之後提供的介面預設實現方法。需要注意的是該兩個方法是支援註解的@Repeatable,而其他方法是不支援的。那麼在平時開發中,我們可以根據自己的專案需求選取不同的方法。

註解的實際使用

通過了解註解的宣告以及與反射機制之間的關係後,現在我們來實戰一下。簡單的寫個例子徹底鞏固註解的相關知識吧。這裡我們簡單通過什麼人在什麼地方做了什麼事為例子:

	//什麼人
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Who {
        String name();
        int age();
    }
    
    //在哪裡
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Where {
        String country();
        String province();
        String city();
    }
	
	//做了什麼事
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface DoSomething {
        String value();
    }
複製程式碼

上述程式碼中,我們宣告瞭三個註解,如果你認真看了前面我們說的註解的定義和使用話的理解起來非常簡單,這裡我們需要注意的是三個註解中的 @Retention都是設定為(RetentionPolicy.RUNTIME),之所以設定為執行時,是因為根據類的載入機制,Class物件的生成是在JVM讀取class檔案的時候,也就是執行期間。那下面我們接著看具體的使用:

@Who(name = "AndyJenifer", age = 18)
class Person {

    @Where(country = "中國", province = "四川", city = "成都")
    private String where;

    @DoSomething("寫部落格")
    public void doSomething() {

    }
    
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        StringBuffer sb = new StringBuffer();

        //獲取類上的註解
        Who who = personClass.getAnnotation(Who.class);
        sb.append(who.name());
        sb.append(who.age());

        //獲取欄位上的註解
        Field[] fields = personClass.getDeclaredFields();
        for (Field field : fields) {
            Annotation[] annotations = field.getAnnotations();
            if (annotations.length > 0 && annotations[0] instanceof Where) {
                Where where = (Where) annotations[0];
                sb.append(where.country());
                sb.append(where.province());
                sb.append(where.city());
            }
        }

        //獲取方法上的註解
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            if (annotations.length > 0 && annotations[0] instanceof DoSomething) {
                DoSomething doSomething = (DoSomething) annotations[0];
                sb.append(doSomething.value());
            }
        }

        System.out.println(sb.toString());

    }
    //輸出結果:AndyJenifer18中國四川成都寫部落格
}
複製程式碼

上述程式碼理解起來還是比較容易,在Main方法中獲取當前Person的Class物件,通過Class物件獲取其中宣告的欄位與方法。得到相應的欄位與方法後,再去拿上面宣告的註解。然後組合資訊並列印。細心的小夥伴肯定觀察到了在獲取相應欄位的時候,我們呼叫的是getDeclaredFields()而不是方法getFields()(當然對於其他元素,如 Constructor、Field、Method、Package,都有類似的方法getDeclaredXXXX()getXXXX())。這裡簡單的說一下這兩種方法的區別:

  • getXXXX():獲得某個類的所有的公共(public)的元素(如Constructor、Field、Method、Package),包括父類宣告的。
  • getDeclaredXXXX():獲得某個類的所有宣告的元素(如Constructor、Field、Method、Package),即包括public、private和proteced,但是不包括父類宣告的

思考

文章到這裡,現在大家已經基本瞭解了註解的宣告與使用。不知道小夥伴們有沒有想過一個問題。如果我們宣告瞭一個註解,然後希望該註解在專案的不同類中都會使用。那麼當處理這些類的註解的時候,我們是不是需要手動的找到所有的Class物件?(不管你是通過類名也好,還是通過檔案的方式來讀取也好)。那這樣是不是會很麻煩呢?在文章中我們也提到過,註解可以用來生成描述符檔案。甚至是新的類定義,並且有助於減輕編寫樣板程式碼的負擔。那麼怎麼通過註解生成新的類的定義呢?又怎麼生成樣板程式碼呢?。如果大家有興趣,我們將在後續文章繼續講述並解決這些問題。

最後

該文章參考以下部落格與圖書,站在巨人的肩膀上。可以看得更遠。

深入理解Java註解型別(@Annotation) 《Think in java 》

相關文章