註解的好處:
1.能夠讀懂別人寫的代碼,特別是框架相關的代碼。
2.本來可能需要很多配置文件,需要很多邏輯才能實現的內容,就可以使用一個或者多個註解來替代,這樣就使得編程更加簡潔,代碼更加清晰。
3.(重點)刮目相看。
(但是怎麼樣才能讓別人刮目相看呢?會用註解不是目的,最重要的是要使用自定義註解來解決問題。)
舉個栗子:
如果面試的時候,你跟老闆說你會使用註解,老闆覺得你這個人還行;但是如果老闆發現你會自定義註解解決問題,老闆肯定就會眼前一亮。
註解這一概念是在java1.5版本提出的,說Java提供了一種原程序中的元素關聯任何信息和任何元數據的途徑的方法。
一、Java中的常見註解
1)JDK註解
JDK註解一共分爲三類:
JDK註解.png
案例:
我們先新建一個接口people,如下:
1 2 3 4 5 | public interface people { public String name(); public int age(); public void work(); } |
然後再建一個類Child實現類people這個接口,並實現該類的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class Child implements people { @Override public String name() { return null ; } @Override public int age() { return 0 ; } @Override public void work() { } |
看到這裏,我們發現這裏的所有方法都會加上一個@Override標記,它告訴我們,同時也告訴編譯器我們的這些方法肯定覆蓋了類people裏面的方法的。假如說,我現在把類people裏面的某一個方法註釋掉:
1 | //public String name(); |
再看類Child裏面的name方法就會報錯。這樣,以後大家看到@Override的時候就能想到這個方法是覆蓋了某個接口的方法的。
然後,我們回過頭來看類people裏面有一個work的方法。這裏我們可以理解爲人是要工作的,但是並不是所有的人都在工作,那麼怎麼辦呢?如果說這個接口正在用,我們不能刪除這個方法,這個時候我們就可以這樣:
1 2 | @Deprecated public void work(); |
@Deprecated標記就表明這個方法已經過時了,在實際中,它又有什麼樣的應用場景呢?我們在建一個測試類:
1 2 3 4 5 6 | public class Test { public void work() { people people= new Child(); ! people.work(); } } |
這個時候我們會發現myeclipse會給一個警告,並且在work中間出現一個破折號,意思就是這個方法已經過時了。那麼問題來了,雖然這個方法過時了,但是我們就是那麼傲嬌,一定要用它,怎麼辦呢?只需要這樣:
1 2 3 4 5 6 7 | public class Test { @SuppressWarnings ( "deprecation" ) public void work() { people people= new Child(); people.work(); } } |
這樣我們就忽略了這個警告。@SuppressWarnings(“deprecation”)就表示我們忽略了deprecation這樣的一個警告。
2)Java第三方註解
第三方註解.png
二、註解的分類
1)按照運行機制劃分:
【源碼註解→編譯時註解→運行時註解】
源碼註解:只在源碼中存在,編譯成.class文件就不存在了。
編譯時註解:在源碼和.class文件中都存在。像前面的@Override、@Deprecated、@SuppressWarnings,他們都屬於編譯時註解。
運行時註解:在運行階段還起作用,甚至會影響運行邏輯的註解。像@Autowired自動注入的這樣一種註解就屬於運行時註解,它會在程序運行的時候把你的成員變量自動的注入進來。
2)按照來源劃分:
【來自JDK的註解——來自第三方的註解——自定義註解】
3)元註解:
元註解是給註解進行註解,可以理解爲註解的註解就是元註解。
三、自定義註解
我們分四步來解析自定義註解:
自定義註解的語法要求:
1 2 3 4 5 6 7 8 9 | @Target ({ElementType.METHOD,ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Description { String desc(); String author(); int age() default 18 ; } |
首先我們要明確這不是一個接口,它是使用@interface關鍵字定義的一個註解。
然後我們看下面的幾個方法,String desc();
雖然它很類似於接口裏面的方法,其實它在註解裏面只是一個成員變量(成員以無參無異常的方式聲明),int age() default 18;
(成員變量可以用default指定一個默認值的)。
最後我們要知道:
①.成員類型是受限制的,合法的類型包括基本的數據類型以及String,Class,Annotation,Enumeration等。
②.如果註解只有一個成員,則成員名必須取名爲value(),在使用時可以忽略成員名和賦值號(=)。
③.註解類可以沒有成員,沒有成員的註解稱爲標識註解。
元註解:
有沒有發現上面那段代碼有一個沒有說呢?沒錯,它們就是我們所說的元註解:
1 2 3 4 | @Target ({ElementType.METHOD,ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Inherited @Documented |
我們先看第一行:@Target是這個註解的作用域,ElementType.METHOD
是這個註解的作用域的列表,METHOD
是方法聲明,除此之外,還有:CONSTRUCTOR(構造方法聲明),FIELD(字段聲明),LOCAL VARIABLE(局部變量聲明),METHOD(方法聲明),PACKAGE(包聲明),PARAMETER(參數聲明),TYPE(類接口)
第二行:@Retention是它的生命週期,前面不是說註解按照運行機制有一個分類嘛,RUNTIME
就是在運行時存在,可以通過反射讀取。除此之外,還有:SOURCE(只在源碼顯示,編譯時丟棄),CLASS(編譯時記錄到class中,運行時忽略),RUNTIME(運行時存在,可以通過反射讀取)
第三行:@Inherited是一個標識性的元註解,它允許子註解繼承它。
第四行:@Documented,生成javadoc時會包含註解。
使用自定義註解:
使用註解的語法:
@<註解名>(<成員名1>=<成員值1>,<成員名1>=<成員值1>,…)
案例:
1 2 3 4 | @Description (desc= "i am Color" ,author= "boy" ,age= 18 ) public String Color() { return "red" ; } |
這裏的Description是我們剛纔在自定義註解語法要求裏面定義的註解噢,然後我們可以給它的每一個成員變量賦值,注意數據類型。值得注意的是,因爲我們前面定義的作用域是在方法和類接口上,所以這個註解在Color()方法上使用是沒問題的。
解析註解
概念:
通過反射獲取類 、函數或成員上的運行時註解信息,從而實現動態控制程序運行的邏輯。
準備工作:
Description類.png
Child類.png
接下來,我們就開始測試了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class ParseAnn { public static void main(String[] args) { try { // 使用類加載器加載類 Class c = Class.forName( "com.test.Child" ); // 找到類上面的註解 boolean isExist = c.isAnnotationPresent(Description. class ); // 上面的這個方法是用這個類來判斷這個類是否存在Description這樣的一個註解 if (isExist) { // 拿到註解實例,解析類上面的註解 Description d = (Description) c.getAnnotation(Description. class ); System.out.println(d.value()); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } |
輸出的結果:i am class annotation
可以看到,我們成功的解析了Child類上面的註解。
接下來,我們繼續解析方法上的註解:
1 2 3 4 5 6 7 8 9 10 | //獲取所有的方法 Method[] ms = c.getMethods(); // 遍歷所有的方法 for (Method m : ms) { boolean isExist1 = m.isAnnotationPresent(Description. class ); if (isExist1) { Description d1=m.getAnnotation(Description. class ); System.out.println(d1.value()); } } |
輸出的結果:i am class annotation
i am method annotation
可以看到,我們成功的解析了方法上面的註解。
1 2 3 4 5 6 7 8 9 10 11 12 | //另一種解析方法 for (Method m : ms) { //拿到方法上的所有的註解 Annotation[] as=m.getAnnotations(); for (Annotation a : as) { //用二元操作符判斷a是否是Description的實例 if (a instanceof Description) { Description d=(Description) a; System.out.println(d.value()); } } } |
也可以得到上面的效果。
此時,如果把Description類裏面的元註解改一下,比如:
@Retention(RetentionPolicy.RUNTIME)→@Retention(RetentionPolicy.SOURCE),再運行程序,結果會成怎樣呢?如果改成CLASS呢?讀者們要不要試一試?
如果看過我寫的《談談JAVA反射機制》——Class類的動態加載的讀者,仔細想一下我們這個環境,就知道爲什麼了。