概念
Java注解是JDK1.5的新特性,與注釋比較類似,不同的是注釋是給我們開發人員看的,注解是給代碼看的,它是代碼層面的解釋說明。
注解的使用也很簡單,文法規則:@注解名稱,比如我們常見的“@Override”。
作用
①生成doc文檔;
②使用反射對代碼進行分析;
③編譯檢查。
JDK中預定義的一些注解:
①@Override: 檢測方法是否是重寫父類或父接口的;
②@Deprecated: 辨別過時的内容;
③@SuppressWarnings: 壓制警告,一般傳入參數all,@SuppressWarnings("all")
自定義注解:
格式:
[元注解]
public @interface 注解名稱{
屬性清單;
}
eg.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface ZnkAnno {
String value();
}
ps:[ ] 表示可選。
注解本質:
通過javap指令反編譯class檔案,可以發現注解的本質就是繼承 java.lang.annotation.Annotation的接口。

注解中的屬性(其實就是接口中的抽象方法):
文法:
1>屬性的傳回值必須是基本資料類型、String、Class、枚舉、注解或者以上類型的數組;
2>定義屬性後,在使用注解時要給屬性指派,如果定義屬性時使用default關鍵字給了屬性預設值,則可以不做指派;如果隻有一個名稱為value的屬性,則value可以省掉,直接定義值即可;數組指派時需要{},如果數組中隻有一個值,則可省略{}。
元注解
元注解就是用來解釋說明注解的注解。
①@Target:描述注解能夠作用的位置。
--ElementType常用取值:
- TYPE:可以作用在類上;
- FIELD:可以作用在屬性上;
- METHOD:可以作用在方法上。
PS:所有取值見下圖。
②@Retention:描述注解被保留的階段(java代碼SOURCE(源碼階段)、CLASS(位元組碼檔案)、RUNTIME(運作時階段)三個階段)
-- @Retention(RetentionPolicy.RUNTIME) 表示目前被描述的注解會被保留到class位元組碼檔案中,并會被JVM讀取到
③@Documented:描述注解是否被抽取到api文檔中
④@Inherited:描述注解是否被子類繼承
注解的使用場景
上面我們已經了解到注解的保留級别包括SOURCE、CLASS、RUNTIME三個階段,那我們根據保留級别不同,自然能想到注解的使用也存在不同場景。
級别 | 技術 | 備注 |
源碼 | APT | 1.在編譯期能夠擷取注解與注解聲明的類,包括類中所有成員資訊,一般用于生成額外的輔助類。 2.也可以用作文法檢查。 |
位元組碼 | 位元組碼增強 | 在編譯出Class後,通過修改Class資料以實作修改代碼邏輯目的。對于是否需要修改的區分或者修改為不同邏輯的判斷可以使用注解。 |
運作時 | 反射 | 在程式運作期間,通過反射技術動态擷取注解與其元素,進而完成不同的邏輯判定。 |
--源碼級别的注解示例:
1>APT(Annotation Processor Tools)
2>文法檢查:在Android開發過程中,當我們需要設計接口供使用者調用時,如果需要對入參類型做限定,比如限定為資源ID、布局ID等類型參數,就可以通過@IdRes、@LayoutRes分别限定。同時我們可以利用@IntDef、@StringDef、@LongDef 定義自己的入參類型檢查。
--位元組碼級别的注解在熱修複技術中會用到,這裡暫時不做較長的描述(關鍵我自己還沒整明白{>-<})。
--運作時的注解主要是與反射結合做一些操作,比如我們常用的ButterKnife,最初就是通過注解加反射的方式實作的,我給出的git代碼裡面也有實作findViewById的案例,感興趣的小夥伴可以瞄一眼。
反射擷取泛型真實類型(很少用到,了解即可)
當我們對泛型類進行反射時,需要拿到泛型資訊來完成像Json反序列化的操作時,需要借助Type體系來完成。Type接口包含一個實作類Class和四個實作接口:
我們開發過程中經常會有這樣一種場景,我們請求背景接口,背景傳回的資料類型可能是這樣的:
{
"data":{
"name":"luffy",
"age":18,
"sex":"male"
},
"code":200,
"message":"查詢成功"
}
我們會将背景傳回的資料封裝成一個Bean:
static class Response<T> {
T data;
int code;
String message;
public Response(T data, int code, String message) {
this.data = data;
this.code = code;
this.message = message;
}
@Override
public String toString() {
return "Response{" +
"data=" + data +
", code=" + code +
", message='" + message + '\'' +
'}';
}
}
因為每個接口傳回的data多數情況下類型是不同的,是以我們一般用泛型處理。比如現在某個接口傳回的資料是
{
"name":"luffy",
"age":18,
"sex":"male"
}
我們将它封裝成Bean:
static class Data {
String name;
int age;
String sex;
public Data(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Data{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
我們測試如上的資料是否可以進行反序列化:如下圖所示,出現了ClassCastException。
解決方案:
Type type = new TypeToken<Response<Data>>() {
}.getType();
Response<Data> response = gson.fromJson(result, type);
我們來看看TypeToken裡面的具體實作:
我也按照這個思路試着實作了一下:
package com.luffy.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 作者甯科CSDN首頁</a><p>
* 建立時間:2020/10/27 15:51<p>
* 描述:參考TypeToken 解決Json反序列化問題
*/
public class TypeRefrence<T> {
Type type;
T t;
protected TypeRefrence() {
//1.獲得泛型類型
Type genericSuperclass = getClass().getGenericSuperclass();
System.out.println("genericSuperclass的類型是: " + genericSuperclass.getClass());
System.out.println("genericSuperclass ====== " + (genericSuperclass instanceof Class));
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//2.因為泛型類型可以定義多個: MainActivity<T,E,K...> 是以傳回是一個數組
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}