注解
聲明一個(gè)注解類型
public @interface Lance{
}
元注解
在定義注解時(shí)奖地,注解類也能夠使用其他的注解聲明。對(duì)注解類型進(jìn)行注解的注解類羹铅,我們稱之為 meta?annotation(元注解)香缺。
- @Target
注解標(biāo)記另一個(gè)注解奥额,以限制可以應(yīng)用注解的 Java 元素類型。目標(biāo)注解指定以下元素類型之一作為其值:
ElementType.ANNOTATION_TYPE 可以應(yīng)用于注解類型访诱。
ElementType.CONSTRUCTOR 可以應(yīng)用于構(gòu)造函數(shù)垫挨。
ElementType.FIELD 可以應(yīng)用于字段或?qū)傩浴?br> ElementType.LOCAL_VARIABLE 可以應(yīng)用于局部變量。
ElementType.METHOD 可以應(yīng)用于方法級(jí)注解触菜。
ElementType.PACKAGE 可以應(yīng)用于包聲明九榔。
ElementType.PARAMETER 可以應(yīng)用于方法的參數(shù)。
ElementType.TYPE 可以應(yīng)用于類的任何元素涡相。 - @Retention
注解指定標(biāo)記注解的存儲(chǔ)方式:
RetentionPolicy.SOURCE - 標(biāo)記的注解僅保留在源級(jí)別中哲泊,并被編譯器忽略。
RetentionPolicy.CLASS - 標(biāo)記的注解在編譯時(shí)由編譯器保留催蝗,但 Java 虛擬機(jī)(JVM)會(huì)忽略切威。
RetentionPolicy.RUNTIME - 標(biāo)記的注解由 JVM 保留,因此運(yùn)行時(shí)環(huán)境可以使用它丙号。
@Retention 三個(gè)值中 SOURCE < CLASS < RUNTIME先朦,即CLASS包含了SOURCE,RUNTIME包含SOURCE犬缨、
CLASS烙无。下文會(huì)介紹他們不同的應(yīng)用場(chǎng)景。
注解應(yīng)用場(chǎng)景
SOURCE
RetentionPolicy.SOURCE 遍尺,作用于源碼級(jí)別的注解截酷,可提供給IDE語(yǔ)法檢查、APT等場(chǎng)景使用乾戏。
在類中使用 SOURCE 級(jí)別的注解迂苛,其編譯之后的class中會(huì)被丟棄。
- IDE語(yǔ)法檢查
在Android開(kāi)發(fā)中鼓择, support-annotations 與 androidx.annotation) 中均有提供 @IntDef 注解三幻,此注解的定義如下:
@Retention(SOURCE) //源碼級(jí)別注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
Java中Enum(枚舉)的實(shí)質(zhì)是特殊單例的靜態(tài)成員變量,在運(yùn)行期所有枚舉類作為單例呐能,全部加載到內(nèi)存中念搬。
比常量多5到10倍的內(nèi)存占用。此注解的意義在于能夠取代枚舉摆出,實(shí)現(xiàn)如方法入?yún)⑾拗啤?/strong>
如:我們定義方法 test 朗徊,此方法接收參數(shù) teacher 需要在:Lance、Alvin中選擇一個(gè)偎漫。如果使用枚舉能夠?qū)崿F(xiàn)為:
public enum Teacher{
LANCE,ALVIN
}
public void test(Teacher teacher) {
}
而現(xiàn)在為了進(jìn)行內(nèi)存優(yōu)化爷恳,我們現(xiàn)在不再使用枚舉,則方法定義為:
public static final int LANCE = 1;
public static final int ALVIN = 2;
public void test(int teacher) {
}
然而此時(shí)象踊,調(diào)用 test 方法由于采用基本數(shù)據(jù)類型int温亲,將無(wú)法進(jìn)行類型限定棚壁。此時(shí)使用@IntDef增加自定義注解:
public static final int LANCE = 1;
public static final int ALVIN = 2;
@IntDef(value = {MAN, WOMEN}) //限定為L(zhǎng)ANCE,ALVIN @Target(ElementType.PARAMETER) //作用于參數(shù)的注解 @Retention(RetentionPolicy.SOURCE) //源碼級(jí)別注解
public @interface Teacher {
}
public void test(@Teacher int teacher) {
}
此時(shí)栈虚,我們?cè)偃フ{(diào)用 test 方法袖外,如果傳遞的參數(shù)不是 LANCE 或者 ALVIN 則會(huì)顯示 Inspection 警告(編譯不會(huì)報(bào)錯(cuò))。
以上注解均為 SOURCE 級(jí)別魂务,本身IDEA/AS 就是由Java開(kāi)發(fā)的在刺,工具實(shí)現(xiàn)了對(duì)Java語(yǔ)法的檢查,借助注解能對(duì)被注解的特定語(yǔ)法進(jìn)行額外檢查头镊。
- APT注解處理器
APT全稱為:"Anotation Processor Tools"蚣驼,意為注解處理器。顧名思義相艇,其用于處理注解颖杏。編寫好的Java源文件,需要經(jīng)過(guò) javac 的編譯坛芽,翻譯為虛擬機(jī)能夠加載解析的字節(jié)碼Class文件留储。注解處理器是 javac 自帶的一個(gè)工具,用來(lái)在編譯時(shí)期掃描處理注解信息咙轩。你可以為某些注解注冊(cè)自己的注解處理器获讳。 注冊(cè)的注解處理器由 javac調(diào)起,并將注解信息傳遞給注解處理器進(jìn)行處理活喊。
注解處理器是對(duì)注解應(yīng)用最為廣泛的場(chǎng)景丐膝。在Glide、EventBus3钾菊、Butterknifer帅矗、Tinker、ARouter等等常用框架中都有注解處理器的身影煞烫。但是你可能會(huì)發(fā)現(xiàn)浑此,這些框架中對(duì)注解的定義并不是 SOURCE 級(jí)別,更多的是 CLASS 級(jí)別滞详,別忘了:CLASS包含了SOURCE凛俱,RUNTIME包含SOURCE、CLASS料饥。
關(guān)于注解處理器的實(shí)現(xiàn)蒲犬,在后續(xù)課程中會(huì)有相當(dāng)多的介紹。此處先不進(jìn)行詳細(xì)介紹稀火。
CLASS
定義為 CLASS 的注解暖哨,會(huì)保留在class文件中赌朋,但是會(huì)被虛擬機(jī)忽略(即無(wú)法在運(yùn)行期反射獲取注解)凰狞。此時(shí)完全符合此種注解的應(yīng)用場(chǎng)景為字節(jié)碼操作篇裁。如:AspectJ、熱修復(fù)Roubust中應(yīng)用此場(chǎng)景赡若。
所謂字節(jié)碼操作即為达布,直接修改字節(jié)碼Class文件以達(dá)到修改代碼執(zhí)行邏輯的目的。在程序中有多處需要進(jìn)行是否登錄的判斷逾冬。
如果我們使用普通的編程方式黍聂,需要在代碼中進(jìn)行 if-else 的判斷,也許存在十個(gè)判斷點(diǎn)身腻,則需要在每個(gè)判斷點(diǎn)加入此項(xiàng)判斷产还。此時(shí),我們可以借助AOP(面向切面)編程思想嘀趟,將程序中所有功能點(diǎn)劃分為: 需要登錄 與 無(wú)需登錄兩種類型脐区,即兩個(gè)切面。對(duì)于切面的區(qū)分即可采用注解她按。
//Java源碼
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
}
@Login
public void jumpA(){
startActivity(new Intent(this,AActivity.class));
}
public void jumpB(){
startActivity(new Intent(this,BActivity.class));
}
在上訴代碼中牛隅, jumpA 方法需要具備登錄身份。而 Login 注解的定義被設(shè)置為 CLASS 酌泰。因此我們能夠在該類所編譯的字節(jié)碼中獲得到方法注解 Login 媒佣。在操作字節(jié)碼時(shí),就能夠根據(jù)方法是否具備該注解來(lái)修改class中該方法的內(nèi)容加入 if-else 的代碼段:
//Class字節(jié)碼
@Login public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}
public void jumpB() {
startActivity(new Intent(this,BActivity.class));
}
注解能夠設(shè)置類型元素(參數(shù))陵刹,結(jié)合參數(shù)能實(shí)現(xiàn)更為豐富的場(chǎng)景默伍,如:運(yùn)行期權(quán)限判定等。
RUNTIME
注解保留至運(yùn)行期衰琐,意味著我們能夠在運(yùn)行期間結(jié)合反射技術(shù)獲取注解中的所有信息巡验。
反射
一般情況下,我們使用某個(gè)類時(shí)必定知道它是什么類碘耳,是用來(lái)做什么的显设,并且能夠獲得此類的引用。于是我們直接對(duì)這個(gè)類進(jìn)行實(shí)例化辛辨,之后使用這個(gè)類對(duì)象進(jìn)行操作捕捂。
反射則是一開(kāi)始并不知道我要初始化的類對(duì)象是什么,自然也無(wú)法使用 new 關(guān)鍵字來(lái)創(chuàng)建對(duì)象了斗搞。這時(shí)候指攒,我們使用 JDK 提供的反射 API 進(jìn)行反射調(diào)用。
反射就是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意方法和屬性;并且能改變它的屬性僻焚。是Java被視為動(dòng)態(tài)語(yǔ)言的關(guān)鍵允悦。
Java反射機(jī)制主要提供了以下功能:
- 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象
- 在運(yùn)行時(shí)獲取或者修改任意一個(gè)類所具有的成員變量和方法
- 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法(屬性)
Gson反序列化
static class Response<T> {
T data;
int code;
String message;
@Override
public String toString() {
return "Response{" + "data=" + data + ", code=" + code + ", message='" + message + '\'' + '}';
}
public Response(T data, int code, String message) {
this.data = data;
this.code = code;
this.message = message;
}
}
static class Data {
String result;
public Data(String result) {
this.result = result;
}
@Override
public String toString() {
return "Data{" + "result=" + result + '}';
}
}
public static void main(String[] args) {
Response<Data> dataResponse = new Response(new Data("數(shù)據(jù)"), 1, "成功");
Gson gson = new Gson(); String json = gson.toJson(dataResponse);
System.out.println(json);
//為什么TypeToken要定義為抽象類?
Response<Data> resp = gson.fromJson(json, new TypeToken<Response<Data>>() { }.getType());
System.out.println(resp.data.result);
}
在進(jìn)行GSON反序列化時(shí)虑啤,存在泛型時(shí)隙弛,可以借助 TypeToken 獲取Type以完成泛型的反序列化架馋。但是為什么TypeToken 要被定義為抽象類呢?
因?yàn)橹挥卸x為抽象類或者接口全闷,這樣在使用時(shí)叉寂,需要?jiǎng)?chuàng)建對(duì)應(yīng)的實(shí)現(xiàn)類,此時(shí)確定泛型類型总珠,編譯才能夠?qū)⒎盒蛃ignature信息記錄到Class元數(shù)據(jù)中屏鳍。
控件綁定
/**
*
*/
public class InjectViewUtils {
public static void injectView(Activity activity) {
if (null == activity) return;
Class<? extends Activity> activityClass = activity.getClass();
Field[] declaredFields = activityClass.getDeclaredFields();
for (Field field : declaredFields) {//獲取Activity類里面聲明的所有成員變量
if (field.isAnnotationPresent(InjectView.class)) {//找出標(biāo)注了@InjectView的成員變量
//解析InjectView 獲取button id
InjectView injectView = field.getAnnotation(InjectView.class);
int value = injectView.value();//獲取變量的值,也就是控件的id
try {
//找到findViewById方法
Method findViewById = activityClass.getMethod("findViewById", int.class);
findViewById.setAccessible(true);
Object view = findViewById.invoke(activity, value);
field.set(activity, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void injectAutowired(Activity activity) {
Class<? extends Activity> cls = activity.getClass();
//獲得數(shù)據(jù)
Intent intent = activity.getIntent();
Bundle extras = intent.getExtras();
if (extras == null) {
return;
}
//獲得此類所有的成員
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
if (field.isAnnotationPresent(Autowired.class)) {
Autowired autowired = field.getAnnotation(Autowired.class);
//獲得key
String key = TextUtils.isEmpty(autowired.value()) ? field.getName() : autowired.value();
if (extras.containsKey(key)) {
Object obj = extras.get(key);
// todo Parcelable數(shù)組類型不能直接設(shè)置局服,其他的都可以.
//獲得數(shù)組單個(gè)元素類型
Class<?> componentType = field.getType().getComponentType();
//當(dāng)前屬性是數(shù)組并且是 Parcelable(子類)數(shù)組
if (field.getType().isArray() &&
Parcelable.class.isAssignableFrom(componentType)) {
Object[] objs = (Object[]) obj;
//創(chuàng)建對(duì)應(yīng)類型的數(shù)組并由objs拷貝
Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) field.getType());
obj = objects;
}
field.setAccessible(true);
try {
field.set(activity, obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}