【每日一題】怎麼讓一個 div 水平垂直居中?
1.1. 概念
Annotation(註解)是 Java 提供的一種對元程式中元素關聯資訊和元資料(metadata)的途徑和方法。Annatation(註解)是一個介面,程式可以通過反射來獲取指定程式中元素的 Annotation物件,然後通過該 Annotation 物件來獲取註解中的元資料資訊。
1.2. 4 種標準元註解
元註解的作用是負責註解其他註解。 Java5.0 定義了 4 個標準的 meta-annotation 型別,它們被用來提供對其它 annotation 型別作說明。
1.2.1. @Target 修飾的物件範圍
- @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 { }
1.2.2. @Retention 定義被保留的時間長短
Retention 定義了該 Annotation 被保留的時間長短:表示需要在什麼級別儲存註解資訊,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效),取值(RetentionPoicy)由:
- @Retention(RetentionPolicy.SOURCE),註解僅存在於原始碼中,在class位元組碼檔案中不包含
- @Retention(RetentionPolicy.CLASS), 預設的保留策略,註解會在class位元組碼檔案中存在,但執行時無法獲得
- @Retention(RetentionPolicy.RUNTIME), 註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到
如果我們是自定義註解,則通過前面分析,我們自定義註解如果只存著原始碼中或者位元組碼檔案中就無法發揮作用,而在執行期間能獲取到註解才能實現我們目的,所以自定義註解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME) public @interface MyTestAnnotation { }
1.2.3 @Documented描述-javadoc
Document的英文意思是文件。它的作用是能夠將註解中的元素包含到 Javadoc 中去。
1.2.4 @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); } }
補充:
從 Java 7 開始,額外添加了 3 個註解:
@SafeVarargs - Java 7 開始支援,忽略任何使用引數為泛型變數的方法或建構函式呼叫產生的警告。
@FunctionalInterface - Java 8 開始支援,標識一個匿名函式或函式式介面。
@Repeatable - Java 8 開始支援,標識某註解可以在同一個宣告上使用多次。
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 { }
通過上面的例子,你可能會有一個疑問,遊戲註解中括號的變數是啥,其實這和遊戲註解中定義的屬性對應。接下來我們繼續學習註解的屬性。
1.3. 註解的屬性
通過上一小節@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(); } } }
詳情參考下圖: