反射,注解中級篇

Java知識是作為Android開發(fā)的語言基礎(chǔ),雖然現(xiàn)在已經(jīng)推出了kotlin,但是基于以下原因我們還是需要好好牢牢掌握

java:

*1. SDK還是java,kotlin也需要編譯成為java運(yùn)行;

\2. Java語言應(yīng)用不僅僅在Android,就是在后臺開發(fā)中也是一個最流行的語言;

\3. 大公司面試都要求我們有扎實(shí)的Java語言基礎(chǔ)。

所以,請大家不要輕視提高自己Java基礎(chǔ)的機(jī)會,請大家認(rèn)真學(xué)習(xí),做好筆記,爭取取得更大的進(jìn)步。

注解

Java 注解(Annotation)又稱 Java 標(biāo)注,是 JDK5.0 引入的一種注釋機(jī)制。 注解是元數(shù)據(jù)的一種形式,提供有關(guān)

于程序但不屬于程序本身的數(shù)據(jù)。注解對它們注解的代碼的操作沒有直接影響。

注解聲明

聲明一個注解類型

Java中所有的注解,默認(rèn)實(shí)現(xiàn) Annotation 接口:

與聲明一個"Class"不同的是,注解的聲明使用 @interface 關(guān)鍵字。一個注解的聲明如下:

元注解

在定義注解時,注解類也能夠使用其他的注解聲明。對注解類型進(jìn)行注解的注解類,我們稱之為 meta?

annotation(元注解)。一般的,我們在定義自定義注解時,需要指定的元注解有兩個 :

package java.lang.annotation;

?

public interface Annotation {

?

boolean equals(Object obj);

?

int hashCode();

?

String toString();

?

Class<? extends Annotation> annotationType();

?

}

?

public @interface Lance{

?

}

另外還有@Documented@Inherited 元注解,前者用于被javadoc工具提取成文檔,后者表示允許子類

繼承父類中定義的注解。

@Target

注解標(biāo)記另一個注解,以限制可以應(yīng)用注解的 Java 元素類型。目標(biāo)注解指定以下元素類型之一作為其值:

ElementType.ANNOTATION_TYPE 可以應(yīng)用于注解類型。

ElementType.CONSTRUCTOR 可以應(yīng)用于構(gòu)造函數(shù)。

ElementType.FIELD 可以應(yīng)用于字段或?qū)傩浴?/p>

ElementType.LOCAL_VARIABLE 可以應(yīng)用于局部變量。

ElementType.METHOD 可以應(yīng)用于方法級注解。

ElementType.PACKAGE 可以應(yīng)用于包聲明。

ElementType.PARAMETER 可以應(yīng)用于方法的參數(shù)。

ElementType.TYPE 可以應(yīng)用于類的任何元素。

@Retention

注解指定標(biāo)記注解的存儲方式:

RetentionPolicy.SOURCE - 標(biāo)記的注解僅保留在源級別中,并被編譯器忽略。

RetentionPolicy.CLASS - 標(biāo)記的注解在編譯時由編譯器保留,但 Java 虛擬機(jī)(JVM)會忽略。

RetentionPolicy.RUNTIME - 標(biāo)記的注解由 JVM 保留,因此運(yùn)行時環(huán)境可以使用它。

@Retention 三個值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、

CLASS。下文會介紹他們不同的應(yīng)用場景。

下面來看例子:

注解類型元素

在上文元注解中,允許在使用注解時傳遞參數(shù)。我們也能讓自定義注解的主體包含 annotation type element (注解

類型元素) 聲明,它們看起來很像方法,可以定義可選的默認(rèn)值。

注意:在使用注解時,如果定義的注解中的類型元素?zé)o默認(rèn)值,則必須進(jìn)行傳值。

//@Target(ElementType.TYPE) 只能在類上標(biāo)記該注解

?

@Target({ElementType.TYPE,ElementType.FIELD}) // 允許在類與類屬性上標(biāo)記該注解

?

@Retention(RetentionPolicy.SOURCE) //注解保留在源碼中

?

public @interface Lance {

?

}

?

@Target({ElementType.TYPE,ElementType.FIELD})

?

@Retention(RetentionPolicy.SOURCE)

?

public @interface Lance {

?

String value(); //無默認(rèn)值

?

int age() default 1; //有默認(rèn)值

?

}

注解應(yīng)用場景

按照@Retention 元注解定義的注解存儲方式,注解可以被在三種場景下使用:

SOURCE

RetentionPolicy.SOURCE ,作用于源碼級別的注解,可提供給IDE語法檢查、APT等場景使用。

在類中使用 SOURCE 級別的注解,其編譯之后的class中會被丟棄。

@Lance("帥") //如果只存在value元素需要傳值的情況,則可以省略:元素名=

@Lance(value="帥",age = 2)

int i;IDE**語法檢查**

在Android開發(fā)中, support-annotations 與 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定義如

下:

Java中Enum(枚舉)的實(shí)質(zhì)是特殊單例的靜態(tài)成員變量,在運(yùn)行期所有枚舉類作為單例,全部加載到內(nèi)存中。

比常量多5到10倍的內(nèi)存占用。

此注解的意義在于能夠取代枚舉,實(shí)現(xiàn)如方法入?yún)⑾拗啤?/p>

@Retention(SOURCE) //源碼級別注解

?

@Target({ANNOTATION_TYPE})

?

public @interface IntDef {

?

int[] value() default {};

?

boolean flag() default false;

?

boolean open() default false;

?

}如:我們定義方法 test ,此方法接收參數(shù) teacher 需要在:**Lance**、**Alvin**中選擇一個。如果使用枚舉能夠?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) {

?

}

?

然而此時,調(diào)用 test 方法由于采用基本數(shù)據(jù)類型int,將無法進(jìn)行類型限定。此時使用@IntDef增加自定義注解:

?

public static final int LANCE = 1;

?

public static final int ALVIN = 2;

?

@IntDef(value = {MAN, WOMEN}) //限定為LANCE,ALVIN

?

@Target(ElementType.PARAMETER) //作用于參數(shù)的注解

?

@Retention(RetentionPolicy.SOURCE) //源碼級別注解

?

public @interface Teacher {

?

}

?

public void test(@Teacher int teacher) {

?

}

此時,我們再去調(diào)用 test 方法,如果傳遞的參數(shù)不是 LANCE 或者 ALVIN 則會顯示 Inspection 警告(編譯不會報

錯)。

可以修改此類語法檢查級別:以上注解均為 SOURCE 級別,本身IDEA/AS 就是由Java開發(fā)的,工具實(shí)現(xiàn)了對Java語法的檢查,借助注解能對被注

解的特定語法進(jìn)行額外檢查。

APT**注解處理器**

APT全稱為:"Anotation Processor Tools",意為注解處理器。顧名思義,其用于處理注解。編寫好的Java源文

件,需要經(jīng)過 javac 的編譯,翻譯為虛擬機(jī)能夠加載解析的字節(jié)碼Class文件。注解處理器是 javac 自帶的一個工

具,用來在編譯時期掃描處理注解信息。你可以為某些注解注冊自己的注解處理器。 注冊的注解處理器由 javac

調(diào)起,并將注解信息傳遞給注解處理器進(jìn)行處理。

注解處理器是對注解應(yīng)用最為廣泛的場景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用

框架中都有注解處理器的身影。但是你可能會發(fā)現(xiàn),這些框架中對注解的定義并不是 SOURCE 級別,更多的

是 CLASS 級別,別忘了:CLASS**包含了SOURCE,RUNTIME包含SOURCE、CLASS。**

關(guān)于注解處理器的實(shí)現(xiàn),在后續(xù)課程中會有相當(dāng)多的介紹。此處先不進(jìn)行詳細(xì)介紹。

CLASS

定義為 CLASS 的注解,會保留在class文件中,但是會被虛擬機(jī)忽略(即無法在運(yùn)行期反射獲取注解)。此時完全符合

此種注解的應(yīng)用場景為字節(jié)碼操作。如:AspectJ、熱修復(fù)Roubust中應(yīng)用此場景。

所謂字節(jié)碼操作即為,直接修改字節(jié)碼Class文件以達(dá)到修改代碼執(zhí)行邏輯的目的。在程序中有多處需要進(jìn)行是否

登錄的判斷。如果我們使用普通的編程方式,需要在代碼中進(jìn)行 if-else 的判斷,也許存在十個判斷點(diǎn),則需要在每個判斷點(diǎn)加

入此項(xiàng)判斷。此時,我們可以借助AOP(面向切面)編程思想,將程序中所有功能點(diǎn)劃分為: 需要登錄 與 無需登錄

兩種類型,即兩個切面。對于切面的區(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é)碼時,就能夠根據(jù)方法是否具備該注解來修改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));

?

}

?

}

注解能夠設(shè)置類型元素(參數(shù)),結(jié)合參數(shù)能實(shí)現(xiàn)更為豐富的場景,如:運(yùn)行期權(quán)限判定等。

RUNTIME

注解保留至運(yùn)行期,意味著我們能夠在運(yùn)行期間結(jié)合反射技術(shù)獲取注解中的所有信息。

反射

一般情況下,我們使用某個類時必定知道它是什么類,是用來做什么的,并且能夠獲得此類的引用。于是我們直接

對這個類進(jìn)行實(shí)例化,之后使用這個類對象進(jìn)行操作。

反射則是一開始并不知道我要初始化的類對象是什么,自然也無法使用 new 關(guān)鍵字來創(chuàng)建對象了。這時候,我們

使用 JDK 提供的反射 API 進(jìn)行反射調(diào)用。反射就是在運(yùn)行狀態(tài)中**,對于任意一個類,都能夠知道這個類的所有屬性和**

方法**;對于任意一個對象,都能夠調(diào)用它的任意方法和屬性;并且能改變它的屬性。**是Java被視為動態(tài)語言的關(guān)鍵。

Java反射機(jī)制主要提供了以下功能:

在運(yùn)行時構(gòu)造任意一個類的對象

在運(yùn)行時獲取或者修改任意一個類所具有的成員變量和方法

在運(yùn)行時調(diào)用任意一個對象的方法(屬性)

Class

反射始于Class,Class**是一個類,封裝了當(dāng)前對象所對應(yīng)的類的信息。**一個類中有屬性,方法,構(gòu)造器等,比如說

有一個Person類,一個Order類,一個Book類,這些都是不同的類,現(xiàn)在需要一個類,用來描述類,這就是

Class,它應(yīng)該有類名,屬性,方法,構(gòu)造器等。Class是用來描述類的類。

Class類是一個對象照鏡子的結(jié)果,對象可以看到自己有哪些屬性,方法,構(gòu)造器,實(shí)現(xiàn)了哪些接口等等。對于每

個類而言,JRE 都為其保留一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個類的有關(guān)信息。 對象只

能由系統(tǒng)建立對象,一個類(而不是一個對象)在 JVM 中只會有一個Class實(shí)例。

獲得 Class 對象

獲取Class對象的三種方式

\1. 通過類名獲取 類名.class

\2. 通過對象獲取 對象名.getClass()

\3. 通過全類名獲取 Class.forName(全類名) classLoader.loadClass(全類名)

使用 Class 類的 forName 靜態(tài)方法

public void jumpB() {

?

startActivity(new Intent(this,BActivity.class));

?

}

public static Class<?> forName(String className)直接獲取某一個對象的 class

調(diào)用某個對象的 getClass() 方法

判斷是否為某個類的實(shí)例

一般地,我們用 instanceof 關(guān)鍵字來判斷是否為某個類的實(shí)例。同時我們也可以借助反射中 Class 對象的

isInstance() 方法來判斷是否為某個類的實(shí)例,它是一個 native 方法:

判斷是否為某個類的類型

創(chuàng)建實(shí)例

通過反射來生成對象主要有兩種方式。

使用Class對象的newInstance()方法來創(chuàng)建Class對象對應(yīng)類的實(shí)例。

先通過Class對象獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建實(shí)例。這

種方法可以用指定的構(gòu)造器構(gòu)造類的實(shí)例。

獲取構(gòu)造器信息

Class<?> klass = int.class;

?

Class<?> classInt = Integer.TYPE;

?

StringBuilder str = new StringBuilder("123");

?

Class<?> klass = str.getClass();

?

public native boolean isInstance(Object obj);

?

public boolean isAssignableFrom(Class<?> cls)

?

Class<?> c = String.class;

?

Object str = c.newInstance();

?

//獲取String所對應(yīng)的Class對象

?

Class<?> c = String.class;

//獲取String類帶一個String參數(shù)的構(gòu)造器

Constructor constructor = c.getConstructor(String.class);

//根據(jù)構(gòu)造器創(chuàng)建實(shí)例

Object obj = constructor.newInstance("23333");

System.out.println(obj);得到構(gòu)造器的方法

獲取類構(gòu)造器的用法與上述獲取方法的用法類似。主要是通過Class類的getConstructor方法得到Constructor類的

一個實(shí)例,而Constructor類有一個newInstance方法可以創(chuàng)建一個對象實(shí)例:

獲取類的成員變量(字段)信息

獲得字段信息的方法

調(diào)用方法

獲得方法信息的方法

當(dāng)我們從類中獲取了一個方法后,我們就可以用 invoke() 方法來調(diào)用這個方法。 invoke 方法的原型為:

利用反射創(chuàng)建數(shù)組

數(shù)組在Java里是比較特殊的一種類型,它可以賦值給一個Object Reference 其中的Array類為

java.lang.reflflect.Array類。我們通過Array.newInstance()創(chuàng)建數(shù)組對象,它的原型是:

反射獲取泛型真實(shí)類型

Constructor getConstructor(Class[] params) -- 獲得使用特殊的參數(shù)類型的public構(gòu)造函數(shù)(包括父類)

Constructor[] getConstructors() -- 獲得類的所有公共構(gòu)造函數(shù)

Constructor getDeclaredConstructor(Class[] params) -- 獲得使用特定參數(shù)類型的構(gòu)造函數(shù)(包括私有)

Constructor[] getDeclaredConstructors() -- 獲得類的所有構(gòu)造函數(shù)(與接入級別無關(guān))

public T newInstance(Object ... initargs)

Field getField(String name) -- 獲得命名的公共字段

Field[] getFields() -- 獲得類的所有公共字段

Field getDeclaredField(String name) -- 獲得類聲明的命名的字段

Field[] getDeclaredFields() -- 獲得類聲明的所有字段

Method getMethod(String name, Class[] params) -- 使用特定的參數(shù)類型,獲得命名的公共方法

Method[] getMethods() -- 獲得類的所有公共方法

Method getDeclaredMethod(String name, Class[] params) -- 使用特寫的參數(shù)類型,獲得類聲明的命名的方法

Method[] getDeclaredMethods() -- 獲得類聲明的所有方法

public Object invoke(Object obj, Object... args)

public static Object newInstance(Class<?> componentType, int length);當(dāng)我們對一個泛型類進(jìn)行反射時,需要的到泛型中的真實(shí)數(shù)據(jù)類型,來完成如json反序列化的操作。此時需要通

過 Type 體系來完成。 Type 接口包含了一個實(shí)現(xiàn)類(Class)和四個實(shí)現(xiàn)接口,他們分別是:

TypeVariable

泛型類型變量??梢苑盒蜕舷孪薜刃畔?;

ParameterizedType

具體的泛型類型,可以獲得元數(shù)據(jù)中泛型簽名類型(泛型真實(shí)類型)

GenericArrayType

當(dāng)需要描述的類型是泛型類的數(shù)組時,比如List[],Map[],此接口會作為Type的實(shí)現(xiàn)。

WildcardType

通配符泛型,獲得上下限信息;

TypeVariable

ParameterizedType

public class TestType <K extends Comparable & Serializable, V> {

?

K key;

?

V value;

?

public static void main(String[] args) throws Exception {

?

// 獲取字段的類型

?

Field fk = TestType.class.getDeclaredField("key");

?

Field fv = TestType.class.getDeclaredField("value");

?

TypeVariable keyType = (TypeVariable)fk.getGenericType();

?

TypeVariable valueType = (TypeVariable)fv.getGenericType();

?

// getName 方法

?

System.out.println(keyType.getName()); // K

?

System.out.println(valueType.getName()); // V

?

// getGenericDeclaration 方法

?

System.out.println(keyType.getGenericDeclaration()); // class com.test.TestType

?

System.out.println(valueType.getGenericDeclaration()); // class com.test.TestType

?

// getBounds 方法

?

System.out.println("K 的上界:"); // 有兩個

?

for (Type type : keyType.getBounds()) { // interface java.lang.Comparable

?

System.out.println(type); // interface java.io.Serializable

?

}

?

System.out.println("V 的上界:"); // 沒明確聲明上界的, 默認(rèn)上界是 Object

?

for (Type type : valueType.getBounds()) { // class java.lang.Object

?

System.out.println(type);

?

}

?

}

?

}**GenericArrayType**

?

**WildcardType**

?

public class TestType {

?

Map<String, String> map;

?

public static void main(String[] args) throws Exception {

?

Field f = TestType.class.getDeclaredField("map");

?

System.out.println(f.getGenericType()); // java.util.Map<java.lang.String,

?

java.lang.String>

?

ParameterizedType pType = (ParameterizedType) f.getGenericType();

?

System.out.println(pType.getRawType()); // interface java.util.Map

?

for (Type type : pType.getActualTypeArguments()) {

?

System.out.println(type); // 打印兩遍: class java.lang.String

?

}

?

}

?

}

?

public class TestType<T> {

?

List<String>[] lists;

?

public static void main(String[] args) throws Exception {

?

Field f = TestType.class.getDeclaredField("lists");

?

GenericArrayType genericType = (GenericArrayType) f.getGenericType();

?

System.out.println(genericType.getGenericComponentType());

?

}

?

}

?

public class TestType {

?

private List<? extends Number> a; // 上限

?

private List<? super String> b; //下限

?

public static void main(String[] args) throws Exception {

?

Field fieldA = TestType.class.getDeclaredField("a");

?

Field fieldB = TestType.class.getDeclaredField("b");

?

// 先拿到范型類型

?

ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();

?

ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();

?

// 再從范型里拿到通配符類型

?

WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];

?

WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];

?

// 方法測試

?

System.out.println(wTypeA.getUpperBounds()[0]); // class java.lang.Number

?

System.out.println(wTypeB.getLowerBounds()[0]); // class java.lang.String

?

// 看看通配符類型到底是什么, 打印結(jié)果為: ? extends java.lang.Number**Gson****反序列化**

?

System.out.println(wTypeA);

?

}

?

}

?

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反序列化時,存在泛型時,可以借助 TypeToken 獲取Type以完成泛型的反序列化。但是為什么

TypeToken 要被定義為抽象類呢?

因?yàn)橹挥卸x為抽象類或者接口,這樣在使用時,需要創(chuàng)建對應(yīng)的實(shí)現(xiàn)類,此時確定泛型類型,編譯才能夠?qū)⒎盒?/p>

signature信息記錄到Class元數(shù)據(jù)中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容