一、前言
注解(Annotation),實際上和屬性、方法一樣,都是一個類的組成部分,不過對於初學者來說還是有點陌生的,因為注解是給別人用的,而屬性和方法都是自己用的,這就導致沒有對注解進行深入的學習,而在使用別人框架的時候,才被迫去了解框架提供的注解的使用方法。
注解的形式都是以@開頭,在微博微信中@Somebody是通知某人,而注解的@Info,則表示通知一件事(表明一種狀態),具體通知誰不去管,具體誰去用也不管,誰對這個注解感興趣誰來用。
1.1 示例
我們從最簡單的例子說起,最常用的就是@Override,@Deprecated,@SuppressWarnings。如果按照上面的說法來說明,這三個注解都是給編譯器使用的。
@Override:表明這個方法是重寫的父類的方法,當你把@Override放到一個方法上時,編譯器會自動去父類中查找是否有相應的方法,如果沒有,說明注解使用錯誤,或者重寫的方法名、參數等寫錯了,那么編譯器就會給出編譯錯誤,讓你去修改。
@Deprecated:表明這個屬性被棄用,當你使用它的時候,編譯器就會給出提醒。
@SuppressWarnings:表明這不是一個警告,那么編譯器就不會把它當做警告給提示出來。
1.2 再議注解
也就是說,注解的使用方便了別人去做某些事情,如果不用注解的話用配置文件也可以,但是針對上面三個注解,如果寫在配置文件中,那么編譯器要怎么知道去哪個配置文件中去讀,又要以怎樣的格式去讀,這都是一個問題,而使用注解遵從了一種約定大於配置的理念。
所以使用注解的時候就要明白這個注解是給誰用的,用作什么。
而當你打算寫一個框架時,也可以提供注解的方式給別人使用,這樣來說更方便,而對於你來說就要以解析注解的方式來代替讀取並解析配置文件的方式。
二、注解的屬性
注解也是有相應的屬性的,也就是說當定義一個注解的時候指定注解的屬性。
2.1 注解位置
首先了解一下可以被注解的位置有哪些,這些都在一個枚舉類:ElementType當中:
- TYPE:類、接口、注解、枚舉
- FIELD:字段
- METHOD:方法
- PARAMETER:參數
- CONSTRUCTOR:構造方法
- LOCAL_VARIABLE:本地變量
- ANNOTATION_TYPE:注解
- PACKAGE:包
- TYPE_PARAMETER:類型參數
- TYPE_USE:類型使用
注解位置配合@Target使用,當只有一個位置時可以這么使用:
@Target(ElementType.ANNOTATION_TYPE)
當指定多個位置時,使用方法如下:
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
如果一個注解沒有指定注解位置,那么它可以應用於所有位置。
2.2 注解生命周期
注解也是有相應的聲明周期的,也是封裝在一個枚舉類:RetentionPolicy中:
- SOURCE:源代碼期間,在編譯時會去除,所以這都是給編譯器使用的
- CLASS:會保留在類文件中,但是運行時JVM不需要保存,默認的生命周期
- RUNTIME:會持續保存到JVM運行時,可以通過反射來獲取
聲明周期配合@Retention來使用,使用方法如下:
@Retention(RetentionPolicy.RUNTIME)
一般來說對於編寫框架用的注解的生命周期都是RUNTIME。
三、自定義注解
3.1 注解定義
注解和接口其實很相似,接口里面的方法定義了行為,注解的方法定義了屬性,下面先給一個例子:
public @interface MyAnnotation { //聲明屬性,可以使用如下類型 String name(); String password() default "123"; int age() default 12; TimeUnit gender() default TimeUnit.SECONDS; Class<?> clazz(); int[] arr() default {1,2,3}; //為了嵌套配置 Override my2(); }
簡單總結下:
- 屬性是以方法的形式定義的,屬性名即為方法名。
- 可以設置默認值,那么當使用該注解的時候,如果不指定該屬性則使用默認這
- 沒有默認值的,使用時必須賦值
- 屬性類型可以為:String、基本類型、Class類型、枚舉、注解、以上的一維數組
- 若只有一種屬性,且名為value,則賦值時可以不指定屬性名
- 同樣的只有一個一維數組value[],則賦值時可以不指定屬性名
3.2 注解使用
當你提供一個注解供別人使用時,那么對方可能將注解應用於允許的位置,並有可能賦值,而我們無法知道注解具體的位置,這時候只能通過反射加遍歷的方式來獲得注解的位置。下面給出一個例子:
package yiwangzhibujian.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; public class UseAnnatation { public static void main(String[] args) throws Exception { Dog dog=new Dog(); Field[] fields = Dog.class.getFields(); for(Field field:fields){ DefaultValue annotation = field.getAnnotation(DefaultValue.class); if(annotation!=null){ String value = annotation.value(); field.set(dog, value); } } System.out.println(dog); } } class Dog{ @DefaultValue("little white") public String name; public int age; public String toString() { return "Dog [name=" + name + ", age=" + age + "]"; } } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface DefaultValue{ String value(); }
這個例子做了如下事:
- 定義一個默認值的注解
- 定義了一個類,指定了一個屬性的默認值
- 構造一個對象的時候,獲取注解並賦默認值
當然這個例子只是舉例說明注解的用法,默認值根本就不用這么復雜的方式,如果用過Spring的話,應該知道自動注入的注解,實現原理就是通過這種方式。
四、jdk已有注解
除了一開始說的@Override,@Deprecated,@SuppressWarnings三個注解以外,jdk還有其他注解,簡單來說,現在介紹的時候就會貼出源碼。
@Documented:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
這個注解用來表明,在生成api文檔的時候將注解的對象生成文檔。
@Inherited:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
被注解的注解,將會有被子類繼承。
@Repeatable:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Repeatable { Class<? extends Annotation> value(); }
被注解的注解,可以在一個屬性上重復使用。
@Native:
@Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Native { }
表明一個字段引用的值可能來自於本地代碼,暫未找到具體示例,后續補上。
五、總結
說到底,注解的使用方還是很簡單的,難點在於提供給你注解的人是怎么通過注解去達到他的目的的。如果你設計一個框架並提供注解給人使用,那么你就要精通反射。不過一般情況下是很少遇到需要自定義反射的場景,除非你設計一個中間框架,別人通過你的框架來調用自己實現的類,就像Spring一樣。