注解介紹
元數(shù)據(jù)
元數(shù)據(jù)就是為其他數(shù)據(jù)提供信息的數(shù)據(jù)
注解
官方解釋:注解用于為代碼提供元數(shù)據(jù)。作為元數(shù)據(jù),注解不直接影響你的代碼執(zhí)行,但也有一些類型的注解實(shí)際上可以用于這一目的。Java 注解是從 JDK 1.5 開始添加到 Java 的。
簡(jiǎn)單的理解:注解就是附加到代碼上的一種額外補(bǔ)充信息
注解作用
源碼階段注解: 編譯器可利用該階段注解檢測(cè)錯(cuò)誤,提示警告信息,打印日志等
編譯階段注解:利用注解信息自動(dòng)生成代碼、文檔或者做其它相應(yīng)的自動(dòng)處理
運(yùn)行階段注解: 可通過(guò)反射獲取注解信息,做相應(yīng)操作
如何自定義定義一個(gè)注解
使用 @interface+ 注解名稱這種語(yǔ)法結(jié)構(gòu)就能定義一個(gè)注解,如下:
@interface TestAnnotation{
}
元注解
元注解就是為注解提供注解的注解
JDK 給我們提供的元注解
1、@Target
2、@Retention
3、@Inherited
4、@Documented
5、@Repeatable
@Target
@Target表示這個(gè)注解能放在什么位置上
ElementType.ANNOTATION_TYPE //能修飾注解
ElementType.CONSTRUCTOR //能修飾構(gòu)造器
ElementType.FIELD //能修飾成員變量
ElementType.LOCAL_VARIABLE //能修飾局部變量
ElementType.METHOD //能修飾方法
ElementType.PACKAGE //能修飾包名
ElementType.PARAMETER //能修飾參數(shù)
ElementType.TYPE //能修飾類、接口或枚舉類型
ElementType.TYPE_PARAMETER //能修飾泛型,如泛型方法、泛型類、泛型接口 (jdk1.8加入)
ElementType.TYPE_USE //能修飾類型 可用于任意類型除了 class (jdk1.8加入)
@Target(ElementType.TYPE)
@interface TestAnnotation{
}
注意:默認(rèn)情況下無(wú)限制
@Retention
@Retention 表示注解的的生命周期,可選的值有 3 個(gè):
RetentionPolicy.SOURCE //表示注解只在源碼中存在,編譯成 class 之后,就沒(méi)了
RetentionPolicy.CLASS //表示注解在 java 源文件編程成 .class 文件后,依然存在,但是運(yùn)行起來(lái)后就沒(méi)了
RetentionPolicy.RUNTIME //表示注解在運(yùn)行起來(lái)后依然存在,程序可以通過(guò)反射獲取這些信息
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation{
}
注意:默認(rèn)情況下為 RetentionPolicy.CLASS
@Inherited
@Inherited表示該注解可被繼承,即當(dāng)一個(gè)子類繼承一個(gè)父類,該父類添加的注解有被 @Inherited修飾,那么子類就可以獲取到該注解,否則獲取不到
@Inherited
@interface TestAnnotation{
}
注意:默認(rèn)情況下為不可繼承
@Documented
@Documented 表示該注解在通過(guò)javadoc 命令生成Api文檔后,會(huì)出現(xiàn)該注解的注釋說(shuō)明
@Documented
@interface TestAnnotation{
}
注意:默認(rèn)情況下為不出現(xiàn)
@Repeatable
@Repeatable是JDK 1.8新增的元注解,它表示注解在同一個(gè)位置能出現(xiàn)多次,這個(gè)注解有點(diǎn)抽象,我們通過(guò)一個(gè)實(shí)際例子理解一下
//游戲玩家注解
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface GamePlayer{
Game[] value();
}
//游戲注解
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(GamePlayer.class)
@interface Game{
String gameName();
}
@Game(gameName = "CF")
@Game(gameName = "LOL")
@Game(gameName = "DNF")
class GameTest{
}
注意:默認(rèn)情況下不可重復(fù)
經(jīng)驗(yàn):通常情況下,我們會(huì)使用多個(gè)元注解組合來(lái)修飾自定義注解
注解屬性
注解屬性類型
1、基本數(shù)據(jù)類型
2、String
3、枚舉類型
4、注解類型
5、Class 類型
6、以上類型的一維數(shù)組類型
定義注解屬性
首先我們定義一些注解屬性,如下:
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface TestAnnotation{
//這就是注解屬性的語(yǔ)法結(jié)構(gòu)
//定義一個(gè)屬性并給了默認(rèn)值
String name() default "erdai";
//定義一個(gè)屬性未給默認(rèn)值
int age();
}
可能你會(huì)有些疑問(wèn):這難道不是在定義方法嗎?還可以給默認(rèn)值?
這些疑問(wèn)先留著,我們繼續(xù)分析
自定義注解默認(rèn)都會(huì)繼承 Annotation ,Annotation 是一個(gè)接口,源碼如下:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
我們知道,在接口中可以定義屬性和方法,那么作為自定義注解,是否也可以定義呢?
可以,接口中的屬性默認(rèn)都是用public static final 修飾的,默認(rèn)是一個(gè)常量,對(duì)于自定義注解來(lái)說(shuō),這點(diǎn)沒(méi)有任何區(qū)別。而接口中的方法其實(shí)就相當(dāng)于自定義注解的屬性,只不過(guò)自定義注解還可以給默認(rèn)值。因此我們?cè)趯W(xué)習(xí)自定義注解屬性時(shí),我們應(yīng)該把它當(dāng)作一個(gè)新知識(shí),加上我剛才對(duì)接口的分析對(duì)比,你上面的那些疑問(wèn)便可以迎刃而解了
注解屬性使用
1、在使用注解的后面接上一對(duì)括號(hào),括號(hào)里面使用 屬性名 = value的格式,多個(gè)屬性之間中間用 ,隔開
2、未給默認(rèn)值的屬性必須進(jìn)行賦值,否則編譯器會(huì)報(bào)紅
//單個(gè)屬性
@TestAnnotation(age = 18)
class Test{
}
//多個(gè)屬性
@TestAnnotation(age = 18,name = "erdai666")
class Test{
}
注解屬性獲取
1、我們?cè)讷@取屬性的時(shí)候,可以先判斷一下是否存在該注解,增強(qiáng)代碼的健壯性,如下:
@TestAnnotation(age = 18,name = "erdai666")
class Test{
}
Class<Test> testClass = Test.class;
//獲取當(dāng)前注解是否存在
boolean annotationPresent = testClass.isAnnotationPresent(TestAnnotation.class);
//如果存在則進(jìn)入條件體
if(annotationPresent){
TestAnnotation declaredAnnotation = testClass.getDeclaredAnnotation(TestAnnotation.class);
System.out.println(declaredAnnotation.name());
System.out.println(declaredAnnotation.age());
}
2、獲取類屬性的注解屬性
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface TestField{
String filed();
}
class Test{
@TestField(filed = "我是屬性")
public String test;
}
//通過(guò)反射獲取屬性注解
Class<Test> testClass1 = Test.class;
try {
Field field = testClass1.getDeclaredField("test");
if(field.isAnnotationPresent(TestField.class)){
TestField fieldAnnotation = field.getDeclaredAnnotation(TestField.class);
System.out.println(fieldAnnotation.filed());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
//打印結(jié)果
我是屬性
3、獲取類方法的注解屬性
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface TestMethod{
String method();
}
class Test{
@TestMethod(method = "我是方法")
public void test(){
}
}
//通過(guò)反射獲取方法注解
Class<Test> testClass2 = Test.class;
try {
Method method = testClass2.getDeclaredMethod("test");
if(method.isAnnotationPresent(TestMethod.class)){
TestMethod methodAnnotation = method.getDeclaredAnnotation(TestMethod.class);
System.out.println(methodAnnotation.method());
}
} catch (Exception e) {
e.printStackTrace();
}
//打印結(jié)果
我是方法
JDK 提供的內(nèi)置注解
JDK 給我們提供了很多內(nèi)置的注解,其中常用的有:
1、@Override
2、@Deprecated
3、@SuppressWarnings
4、@FunctionalInterface
@Override
@Override 用在方法上,表示這個(gè)方法重寫了父類的方法,例如toString 方法
@Override
public String toString() {
return super.toString();
}
@Deprecated

可以看到用 @Deprecated 注解的方法調(diào)用的時(shí)候會(huì)被劃掉
@SuppressWarnings
@SuppressWarnings 用于忽略警告信息,常見的取值如下:
deprecation:使用了不贊成使用的類或方法時(shí)的警告(使用@Deprecated使得編譯器產(chǎn)生的警告)
unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告,例如當(dāng)使用集合時(shí)沒(méi)有用泛型 (Generics) 來(lái)指定集合保存的類型; 關(guān)閉編譯器警告
fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒(méi)有 Break 時(shí)的警告
path:在類路徑、源文件路徑等中有不存在的路徑時(shí)的警告
serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時(shí)的警告
finally:任何 finally 子句不能正常完成時(shí)的警告
rawtypes: 泛型類型未指明
unused:引用定義了,但是沒(méi)有被使用
all:關(guān)于以上所有情況的警告
以泛型舉個(gè)例子:

當(dāng)我們創(chuàng)建 List 未指定泛型時(shí),編譯器就會(huì)報(bào)黃提示我們未指明泛型,這個(gè)時(shí)候就可以使用這個(gè)注解了:

@FunctionalInterface
@FunctionalInterface是 JDK 1.8 新增的注解,用于約定函數(shù)式接口,函數(shù)式接口就是接口中只有一個(gè)抽象方法
@FunctionalInterface
interface testInterface{
void testMethod();
}

注解實(shí)際應(yīng)用場(chǎng)景
使用自定義注解代替枚舉類型
主要針對(duì)源碼階段注解
這個(gè)在我們實(shí)際工作中也挺常用的,使用枚舉類型開銷大,我們一般都會(huì)使用自定義注解進(jìn)行替代,如下:
//1、使用枚舉
enum EnumFontType{
ROBOTO_REGULAR,ROBOTO_MEDIUM,ROBOTO_BOLD
}
//實(shí)際調(diào)用
EnumFontType type1 = EnumFontType.ROBOTO_BOLD;
//================================ 完美的分割線 ==================================
//2、使用自定義注解
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
@IntDef({AnnotationFontType.ROBOTO_REGULAR,AnnotationFontType.ROBOTO_MEDIUM,AnnotationFontType.ROBOTO_BOLD})
@interface AnnotationFontType{
int ROBOTO_REGULAR = 1;
int ROBOTO_MEDIUM = 2;
int ROBOTO_BOLD = 3;
}
//實(shí)際調(diào)用
@AnnotationFontType int type2 = AnnotationFontType.ROBOTO_MEDIUM;
注解處理器 (APT)
主要針對(duì)編譯階段注解
實(shí)際我們?nèi)粘i_發(fā)中,經(jīng)常會(huì)遇到它,因?yàn)槲覀兂S玫囊恍╅_源庫(kù)如 ButterKnife,Retrofit,Arouter,EventBus 等等都使用到了APT 技術(shù)。也正是因?yàn)檫@些著名的開源庫(kù),才使得 APT技術(shù)越來(lái)越火,在本系列的下一篇中,我也會(huì)講到。
運(yùn)行時(shí)注解處理
主要針對(duì)運(yùn)行階段注解
舉個(gè)實(shí)際的例子:例如我們開車去自助加油機(jī)加油,設(shè)定的Money是 200,如果少于 200則提示 加油中...,否則提示 油已加滿,如果出現(xiàn)異常情況,提示 加油失敗
現(xiàn)在我們通過(guò)注解來(lái)實(shí)現(xiàn)一下它,如下:
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface OilAnnotation{
double maxOilMoney() default 0;
}
class GasStation{
@OilAnnotation(maxOilMoney = 200)
public void addOil(double money){
String tips = processOilAnnotation(money);
System.out.println(tips);
}
@SuppressWarnings("all")
private String processOilAnnotation(double money){
try {
Class<GasStation> aClass = GasStation.class;
//獲取當(dāng)前方法的注解
Method addOilMethod = aClass.getDeclaredMethod("addOil", double.class);
//獲取方法注解是否存在
boolean annotationPresent = addOilMethod.isAnnotationPresent(OilAnnotation.class);
if(annotationPresent){
OilAnnotation oilAnnotation = addOilMethod.getDeclaredAnnotation(OilAnnotation.class);
if(money >= oilAnnotation.maxOilMoney()){
return "油已加滿";
}else {
return "加油中...";
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return "加油失敗";
}
}
new GasStation().addOil(100);
//打印結(jié)果
加油中...
new GasStation().addOil(200);
//打印結(jié)果
油已加滿
總結(jié)
本篇文章講的一些重點(diǎn)內(nèi)容:
1、自定義注解時(shí),元注解的組合使用
2、注解屬性的定義,使用和獲取
3、一些常用的 JDK 內(nèi)置注解
4、注解的實(shí)際應(yīng)用及運(yùn)行階段注解的一個(gè)實(shí)踐