注解介紹
元數(shù)據(jù)
元數(shù)據(jù)就是為其他數(shù)據(jù)提供信息的數(shù)據(jù)
注解
官方解釋:注解用于為代碼提供元數(shù)據(jù)炼杖。作為元數(shù)據(jù)蒂教,注解不直接影響你的代碼執(zhí)行蜓氨,但也有一些類型的注解實際上可以用于這一目的月杉。Java 注解是從 JDK 1.5 開始添加到 Java 的盗痒。
簡單的理解:注解就是附加到代碼上的一種額外補(bǔ)充信息
注解作用
源碼階段注解: 編譯器可利用該階段注解檢測錯誤横漏,提示警告信息谨设,打印日志等
編譯階段注解:利用注解信息自動生成代碼、文檔或者做其它相應(yīng)的自動處理
運(yùn)行階段注解: 可通過反射獲取注解信息缎浇,做相應(yīng)操作
如何自定義定義一個注解
使用 @interface
+ 注解名稱
這種語法結(jié)構(gòu)就能定義一個注解扎拣,如下:
@interface TestAnnotation{
}
元注解
元注解就是為注解提供注解的注解
JDK 給我們提供的元注解
1、@Target
2、@Retention
3二蓝、@Inherited
4誉券、@Documented
5、@Repeatable
@Target
@Target
表示這個注解能放在什么位置上
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)情況下無限制
@Retention
@Retention
表示注解的的生命周期琴锭,可選的值有 3 個:
RetentionPolicy.SOURCE //表示注解只在源碼中存在,編譯成 class 之后衙传,就沒了
RetentionPolicy.CLASS //表示注解在 java 源文件編程成 .class 文件后决帖,依然存在,但是運(yùn)行起來后就沒了
RetentionPolicy.RUNTIME //表示注解在運(yùn)行起來后依然存在蓖捶,程序可以通過反射獲取這些信息
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation{
}
注意:默認(rèn)情況下為 RetentionPolicy.CLASS
@Inherited
@Inherited
表示該注解可被繼承地回,即當(dāng)一個子類繼承一個父類,該父類添加的注解有被 @Inherited
修飾俊鱼,那么子類就可以獲取到該注解刻像,否則獲取不到
@Inherited
@interface TestAnnotation{
}
注意:默認(rèn)情況下為不可繼承
@Documented
@Documented
表示該注解在通過javadoc
命令生成Api
文檔后,會出現(xiàn)該注解的注釋說明
@Documented
@interface TestAnnotation{
}
注意:默認(rèn)情況下為不出現(xiàn)
@Repeatable
@Repeatable
是JDK 1.8
新增的元注解并闲,它表示注解在同一個位置能出現(xiàn)多次细睡,這個注解有點抽象,我們通過一個實際例子理解一下
//游戲玩家注解
@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)驗:通常情況下帝火,我們會使用多個元注解組合來修飾自定義注解
注解屬性
注解屬性類型
1溜徙、基本數(shù)據(jù)類型
2、String
3犀填、枚舉類型
4蠢壹、注解類型
5、Class 類型
6九巡、以上類型的一維數(shù)組類型
定義注解屬性
首先我們定義一些注解屬性图贸,如下:
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface TestAnnotation{
//這就是注解屬性的語法結(jié)構(gòu)
//定義一個屬性并給了默認(rèn)值
String name() default "erdai";
//定義一個屬性未給默認(rèn)值
int age();
}
可能你會有些疑問:這難道不是在定義方法嗎?還可以給默認(rèn)值冕广?
這些疑問先留著疏日,我們繼續(xù)分析
自定義注解默認(rèn)都會繼承 Annotation
,Annotation
是一個接口撒汉,源碼如下:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
我們知道沟优,在接口中可以定義屬性和方法,那么作為自定義注解神凑,是否也可以定義呢净神?
可以何吝,接口中的屬性默認(rèn)都是用public static final
修飾的,默認(rèn)是一個常量鹃唯,對于自定義注解來說爱榕,這點沒有任何區(qū)別。而接口中的方法其實就相當(dāng)于自定義注解的屬性坡慌,只不過自定義注解還可以給默認(rèn)值黔酥。因此我們在學(xué)習(xí)自定義注解屬性時,我們應(yīng)該把它當(dāng)作一個新知識洪橘,加上我剛才對接口的分析對比跪者,你上面的那些疑問便可以迎刃而解了
注解屬性使用
1、在使用注解的后面接上一對括號熄求,括號里面使用 屬性名 = value
的格式渣玲,多個屬性之間中間用 ,隔開
2、未給默認(rèn)值的屬性必須進(jìn)行賦值弟晚,否則編譯器會報紅
//單個屬性
@TestAnnotation(age = 18)
class Test{
}
//多個屬性
@TestAnnotation(age = 18,name = "erdai666")
class Test{
}
注解屬性獲取
1忘衍、我們在獲取屬性的時候,可以先判斷一下是否存在該注解卿城,增強(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;
}
//通過反射獲取屬性注解
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(){
}
}
//通過反射獲取方法注解
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
用在方法上便斥,表示這個方法重寫了父類的方法至壤,例如toString
方法
@Override
public String toString() {
return super.toString();
}
@Deprecated
可以看到用 @Deprecated 注解的方法調(diào)用的時候會被劃掉
@SuppressWarnings
@SuppressWarnings
用于忽略警告信息威始,常見的取值如下:
deprecation
:使用了不贊成使用的類或方法時的警告(使用@Deprecated
使得編譯器產(chǎn)生的警告)
unchecked
:執(zhí)行了未檢查的轉(zhuǎn)換時的警告枢纠,例如當(dāng)使用集合時沒有用泛型 (Generics
) 來指定集合保存的類型; 關(guān)閉編譯器警告
fallthrough
:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時的警告
path
:在類路徑、源文件路徑等中有不存在的路徑時的警告
serial
:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時的警告
finally
:任何 finally 子句不能正常完成時的警告
rawtypes
: 泛型類型未指明
unused
:引用定義了黎棠,但是沒有被使用
all
:關(guān)于以上所有情況的警告
以泛型舉個例子:
當(dāng)我們創(chuàng)建 List 未指定泛型時晋渺,編譯器就會報黃提示我們未指明泛型,這個時候就可以使用這個注解了:
@FunctionalInterface
@FunctionalInterface
是 JDK 1.8
新增的注解脓斩,用于約定函數(shù)式接口木西,函數(shù)式接口就是接口中只有一個抽象方法
@FunctionalInterface
interface testInterface{
void testMethod();
}
注解實際應(yīng)用場景
使用自定義注解代替枚舉類型
主要針對源碼階段注解
這個在我們實際工作中也挺常用的,使用枚舉類型開銷大随静,我們一般都會使用自定義注解進(jìn)行替代八千,如下:
//1吗讶、使用枚舉
enum EnumFontType{
ROBOTO_REGULAR,ROBOTO_MEDIUM,ROBOTO_BOLD
}
//實際調(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;
}
//實際調(diào)用
@AnnotationFontType int type2 = AnnotationFontType.ROBOTO_MEDIUM;
注解處理器 (APT)
主要針對編譯階段注解
實際我們?nèi)粘i_發(fā)中恋捆,經(jīng)常會遇到它照皆,因為我們常用的一些開源庫如 ButterKnife
,Retrofit
沸停,Arouter
膜毁,EventBus
等等都使用到了APT
技術(shù)。也正是因為這些著名的開源庫愤钾,才使得 APT
技術(shù)越來越火瘟滨,在本系列的下一篇中,我也會講到能颁。
運(yùn)行時注解處理
主要針對運(yùn)行階段注解
舉個實際的例子:例如我們開車去自助加油機(jī)加油杂瘸,設(shè)定的Money
是 200
,如果少于 200
則提示 加油中...
伙菊,否則提示 油已加滿
胧沫,如果出現(xiàn)異常情況,提示 加油失敗
現(xiàn)在我們通過注解來實現(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é)
本篇文章講的一些重點內(nèi)容:
1绒怨、自定義注解時,元注解的組合使用
2谦疾、注解屬性的定義南蹂,使用和獲取
3、一些常用的 JDK 內(nèi)置注解
4念恍、注解的實際應(yīng)用及運(yùn)行階段注解的一個實踐