由淺入深 帶你瞭解 JAVA 註解
在學習註解之前,我首先來講一講學習註解的好處,不管下面看不看,先打個雞血先。不過確定的是,在正常 JAVA 開發中,自己寫註解是比較少的,更多的情況就是使用第三方庫的註解,正因為如此,大多數開發者對於註解僅僅停留在會用的地步。試想一下,當大多數人都不會的時候你會,那麼你是不是就超越的大部分人。除此之外,我還總結學習註解的3大好處:
- 能夠讀懂別人寫的程式碼,特別是框架相關的程式碼;
- 讓程式設計更加簡潔,程式碼更加清晰;
- 讓別人高看一眼,裝逼利器;
說完了這些,下面就開始真乾貨了。
註解概念
JDK 5中引入了原始碼中的註解(annotation)這一機制。 註解使得Java原始碼中不但可以包含功能性的實現程式碼,還可以新增元資料。 註解的功能類似於程式碼中的註釋,所不同的是註解不是提供程式碼功能的說明,而是實現程式功能的重要組成部分。
Java中常見註解
對於常見註解,單單看在開發 JAVA 時 IDE 彈出的那些註解就能瞭解到,對於JDK 的註解,我們是要非常熟悉才行。
JDK自帶註解
@Override
@Deprecated
@Suppvisewarnings
常見第三方註解
Spring
@Autowired
@Service
@Repository
Mybatis
@InsertProvider
@UpdateProvider
@Options
註解的分類
按照執行機制分類
- 原始碼註解 (註解只在原始碼中存在,編譯成
.class
檔案就不存在了) - 編譯時註解 (註解在原始碼和
.class
- 執行時註解 (在執行階段還起作用,甚至會影響執行邏輯的註解,例如
@Autowired
)
按照來源分類
- 來自JDK的註解
- 來自第三方的註解
- 自定義註解
元註解
給註解使用的註解
自定義註解
下面是一個典型的註解宣告,大家先看個大概,下面會對定義註解的注意事項做個說明:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String desc();
String author();
int age() default 18;
}
- 使用
@interface
關鍵字定義註解 - 成員(變數)以無引數無異常方式宣告
- 可以用
default
為成員指定一個預設值 - 註解中成員的型別是受限制的,合法的型別包括基本資料型別以及
String
、Class
、Annotation
和Enumeration
- 如果註解只有一個成員,則成員名應該為
value()
,在使用時可以忽略成員名和賦值號(=
) - 註解類可以沒有成員,沒有成員的註解稱為標識註解
- 元註解,用於對註解進行的註解,例如
Description
註解上面的4個註解
常用元註解
Target
標識註解的作用域,作用域有以下幾種,幾乎包含了 JAVA 所有的型別:
1. ElementType.CONSTRUCTOR
:構造方法
2. ElementType.FIELD
:欄位
3. ElementType.LOCAL_VARIABLE
:區域性變數
4. ElementType.METHOD
:方法
5. ElementType.PACKAGE
:包
6. ElementType.PARAMETER
: 引數
7. ElementType.TYPE
:類、介面
例如上面例子中標識了Description
可以用於對方法和類或介面進行註解:
@Target({ElementType.PARAMETER, ElementType.TYPE})
Retention
標識註解的生命週期,有以下3種值:
1. RetentionPolicy.SOURCE
:只在原始碼顯示,編譯時會丟棄
2. RetentionPolicy.CLASS
:編譯時會記錄到class
檔案中,執行時會忽略
3. RetentionPolicy.RUNTIME
:執行時存在,可以通過反射讀取
例如上面的例子中標識了Description
可以記錄到class
檔案中,但在執行時會忽略:
@Retention(RetentionPolicy.CLASS)
Inherited
標識性元註解,標識該註解允許子類進行繼承。注意這裡可不是註解的繼承,註解之間也沒有繼承什麼的。這裡指的是如果一個類(不是介面)宣告上使用了這個註解,那麼當它的一個子類繼承該類時,也會擁有與父類一樣擁有該註解。
Documented
標識在生成 JAVA DOC 時會包含該註解
使用自定義註解
使用註解的語法:
@<註解名>(<成員名1>=<成員值1>, <成員名1>=<成員值1>, <成員名1>=<成員值1>, ...)
其中的成員名則對應了註解裡的成員,例子:
@Description(desc="description", author="swifter", age=18)
public String getColor() {
return "red";
}
上面只是個簡單的例子,下面給出一個註解的詳細使用方式,這也是在正常開發中會使用到的方式。對於註解,上面的程式碼中已經給出了一個宣告,下面就寫兩個類來使用該註解。通過這兩個例子,怎麼使用註解就顯而易見了。
第一個類 Person
:
@Description(desc="person interface", author="swifter")
public abstract class Person {
@Description(desc="method getName", author="swifter")
abstract String getName();
abstract void doSomething();
}
第二個類 Child
繼承自 Person
:
public class Child extends Person {
@Override
@Description(desc="child method getName", author="swifter")
public String getName() {
return "get child";
}
@Override
public void doSomething() {
System.out.println("do something in child class");
}
}
解析註解
既然已經定義了註解,那麼就應該考慮如何在程式碼中解析這個註解了。解析註解就是通過反射獲取類、函式或成員上的執行時註解資訊,從而實現動態控制程式執行的邏輯。
解析主要方式就是通過反射的途徑,通過Class
物件來獲取註解,並拿到註解的欄位再進行操作。例如下面的例子:
Class<Child> clazz = Child.class;
if(clazz.isAnnotationPresent(Description.class)) {
Description description = clazz.getAnnotation(Description.class);
System.out.println(description.desc()+" : "+description.author());
}
Method[] methods = clazz.getMethods();
for(Method method : methods) {
if(method.isAnnotationPresent(Description.class)) {
Description description = method.getAnnotation(Description.class);
System.out.println(description.desc()+" : "+description.author());
}
}
執行之後客戶端會給出如下結果:
person interface : swifter
child method getName : swifter
其中第一行顯示的是類上面的註解資訊,第二行則顯示的是方法上的註解資訊。對於這兩個註解的物件,在程式碼中可以看到,都是通過反射的形式拿到的。雖然說反射的方式在效率上比較慢,但是想想註解帶來的優點,掌握註解還是有很大必要的。