自我提升(基礎技術篇)——反射和注解

前言,本來只是想研究一下注解的,不過發現,要懂注解先得懂反射,別問我為什么,你可以自己試試

JAVA反射

主要是指程序可以訪問,檢測和修改它本身狀態或行為的一種能力,并能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。

反射機制是什么

反射機制就是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。

用一句話總結就是反射可以實現在運行時可以知道任意一個類屬性和方法。(所以,其實我們使用private,并沒有那么安全,因為有反射,但是,java'的同學還是用吧)

反射機制能做什么

在運行時判斷任意一個對象所屬的類;

在運行時構造任意一個類的對象;

在運行時判斷任意一個類所具有的成員變量和方法;

在運行時調用任意一個對象的方法;

生成動態代理

這里不多說,后面詳細說。

Java 反射機制的應用場景

逆向代碼 ,例如反編譯

與注解相結合的框架 例如Retrofit

單純的反射機制應用框架 例如EventBus

動態生成類框架 例如Gson

此刻心中是不是有一句“臥槽,這么吊,這些東西都用到了反射”。沒錯就這么吊!后面,就會發現,他真的這么吊。

反射機制的優缺點

說優缺點,之前,先說兩個概念:靜態編譯,動態編譯

靜態編譯:在編譯時確定類型,綁定對象。

動態編譯:運行時確定類型,綁定對象。

而反射,屬于動態編譯。這樣最大限度發揮了java的靈活性,體現了多態的應用,有以降低類之間的藕合性。

優點

可以實現動態創建對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟件,不可能一

次就把把它設計的很完美,當這個程序編譯后,發布了,當發現需要更新某些功能時,我們不可能要用戶把以前的卸載,再重新安裝新的版本,

假如這樣的話,這個軟件肯定是沒有多少人用的。采用靜態的話,需要把整個程序重新編譯一次才可以實現功能的更新,而采用反射機制的話,

它就可以不用卸載,只需要在運行時才動態的創建和編譯,就可以實現該功能。

缺點

對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執行相同的操作。

理解Class類和類類型

想要了解反射首先理解一下Class類,它是反射實現的基礎

類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)

對于普通的對象,我們一般都會這樣創建和表示:

Code code1 =new Code();

上面說了,所有的類都是Class的對象,那么如何表示呢,可不可以通過如下方式呢:

Class c =new Class();

很遺憾,不是這樣寫的,源碼是:

private Class(ClassLoader loader){

classLoader = loader;

}

可以看到構造器是私有的,只有JVM可以創建Class的對象,因此不可以像普通類一樣new一個Class對象,雖然我們不能new一個Class對象,但

是卻可以通過已有的類得到一個Class對象,共有三種方式,如下:

Class c1 = Code.class; 這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是通過獲取類的靜態成員變量class得到的

Class c2= code1.getClass(); code1是Code的一個對象,這種方式是通過一個類的對象的getClass()方法獲得的

Class c3 = Class.forName("com.trigl.reflect.Code"); 這種方法是Class類調用forName方法,通過一個類的全量限定名獲得

這里,c1、c2、c3都是Class的對象,他們是完全一樣的,而且有個學名,叫做Code的類 類型(class type)。

這里就讓人奇怪了,前面不是說Code是Class的對象嗎,而c1、c2、c3也是Class的對象,那么Code和c1、c2、c3不就一樣了嗎?為什么還叫

Code什么類 類型?這里不要糾結于它們是否相同,只要理解類 類型是干什么的就好了,顧名思義,類 類型就是類的類型,也就是描述一個類是什么,

都有哪些東西,所以我們可以通過類 類型知道一個類的屬性和方法,并且可以調用一個類的屬性和方法,這就是反射的基礎

Java反射相關操作

在這里先看一下sun為我們提供了那些反射機制中的類:

java.lang.Class;

java.lang.reflect.Constructor; java.lang.reflect.Field;

java.lang.reflect.Method;

java.lang.reflect.Modifier;

ok,現在,你已經知道怎么獲取這個Class了吧,那么,這玩意,可以用來干嘛呢?

獲取成員方法Method

獲取成員變量Field

獲取構造函數Constructor

獲取成員方法信息

兩個參數分別是方法名和方法參數類的類類型列表。

public Method getDeclaredMethod(String name, Class... parameterTypes)// 得到該類所有的方法,不包括父類的

public Method getMethod(String name, Class... parameterTypes)// 得到該類所有的public方法,包括父類的

//具體使用

Method[] methods= class1.getDeclaredMethods();//獲取class對象的所有聲明方法

Method[] allMethods = class1.getMethods();//獲取class對象的所有public方法 包括父類的方法

Method method = class1.getMethod("info", String.class);//返回次Class對象對應類的、帶指定形參列表的public方法

Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class對象對應類的、帶指定形參列表的方法

舉個例子來說明一下

public void fun(String name,int age){

System.out.println("我叫"+name+",今年"+age+"歲");

}

現在知道A有一個對象a,那么就可以通過:

Class c = Class.forName("com.tengj.reflect.Person");//先生成class

Object o = c.newInstance();//newInstance可以初始化一個實例

Method method = c.getMethod("fun", String.class,int.class);//獲取方法

method.invoke(o,"tengj",10);

執行結果:

我叫tengj,今年10歲

是不是很神奇?沒錯就這么神奇,首先,獲取Calss對象,然后用這個Class的newInstance方法,創造一個實例對象,然后這個實例去獲取Class中的fun方法(注意,由于方法是可以重載的,所以這里要把參數帶上,才能知道具體是哪一個方法),最后,invoke方法,就實現了調用。

這個時候,或許會問了,那如果有很多方法,我不是要累死呀,這樣寫。ok,接著看,有高招。

下面先來一個完整的類


獲取全部方法的方法:

1.獲取所有方法的數組:

Class c = Class.forName("com.tengj.reflect.Person");

Method[] methods = c.getDeclaredMethods();// 得到該類所有的方法,不包括父類的

或者:

Method[] methods = c.getMethods();// 得到該類所有的public方法,包括父類的

2.然后循環這個數組就得到每個方法了:

for(Method method : methods)

ok,最后,還是來個例子吧:




ok,就這么簡單,就得到了說有的方法了。下面再來看看如何獲取成員變量

獲取成員變量信息

想一想成員變量中都包括什么:成員變量類型+成員變量名(String name)

類的成員變量也是一個對象(萬物皆對象),它是java.lang.reflect.Field的一個對象,所以我們通過java.lang.reflect.Field里面封裝的方法來獲取這些信息。

單獨獲取某個成員變量,通過Class類的以下方法實現:

參數是成員變量的名字

還是舉個例子吧


執行結果:hello wrold(看之前那個person類)

相信說到這里,聰明的同學已經想到,要獲取說有的成員變量的方法了吧,沒錯,循環遍歷


結果是:

name

age

msg

獲取構造函數

首先,想想想,構造函數是什么樣的?

構造函數是java.lang.reflect.Constructor的一個對象,所以我們通過java.lang.reflect.Constructor里面封裝的方法來獲取這些信息。

獲取某個構造函數,通過Class類的以下方法實現:

public Constructor?getDeclaredConstructor(Class... parameterTypes)//? 獲得該類所有的構造器,不包括其父類的構造器

public Constructor?getConstructor(Class... parameterTypes)// 獲得該類所以public構造器,包括父類

//具體

Constructor[] allConstructors= class1.getDeclaredConstructors();//獲取class對象的所有聲明構造函數

Constructor[] publicConstructors = class1.getConstructors();//獲取class對象public構造函數

Constructor constructor = class1.getDeclaredConstructor(String.class);//獲取指定聲明構造函數

Constructor publicConstructor = class1.getConstructor(String.class);//獲取指定聲明的public構造函數

下面還是舉個例子,演示一下


注意:Class的newInstance方法,只能創建只包含無參數的構造函數的類,所以文中用了Constructor來創建,打開Class類發現,里面只有一個無參的newInstance


如果某類只有帶參數的構造函數,那么就要使用另外一種方式:

fromClass.getDeclaredConstructor(String.class).newInstance("hello");

獲取所有的構造函數,可以通過以下步驟實現:

1.獲取該類的所有構造函數,放在一個數組中:

Constructor[] constructors = c.getDeclaredConstructors();

2.遍歷構造函數數組,獲得某個構造函數constructor:

for(Constructor constructor : constructors)

看一個實例吧:


其他方法

上面講的,都是一些反射的基礎方法,下面講一些常用的方法和應用。

注解需要用到的:(了解這些方法,是我們最后用注解的基礎)

Annotation[] annotations = (Annotation[]) class1.getAnnotations();//獲取class對象的所有注解

Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//獲取class對象指定注解

Type genericSuperclass = class1.getGenericSuperclass();//獲取class對象的直接超類的

Type Type[] interfaceTypes = class1.getGenericInterfaces();//獲取class對象的所有接口的type集合

獲取class對象的信息

boolean isPrimitive = class1.isPrimitive();//判斷是否是基礎類型

boolean isArray = class1.isArray();//判斷是否是集合類

boolean isAnnotation = class1.isAnnotation();//判斷是否是注解類

boolean isInterface = class1.isInterface();//判斷是否是接口類

boolean isEnum = class1.isEnum();//判斷是否是枚舉類

boolean isAnonymousClass = class1.isAnonymousClass();//判斷是否是匿名內部類

boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判斷是否被某個注解類修飾

String className = class1.getName();//獲取class名字 包含包名路徑

Package aPackage = class1.getPackage();//獲取class的包信息

String simpleName = class1.getSimpleName();//獲取class類名

int modifiers = class1.getModifiers();//獲取class訪問權限

Class[] declaredClasses = class1.getDeclaredClasses();//內部類

Class declaringClass = class1.getDeclaringClass();//外部類

getSuperclass():獲取某類的父類

getInterfaces():獲取某類實現的接口

通過反射了解集合泛型的本質

上述,只是簡單介紹了一些方法,具體怎么用,會在后面再說,這里,先來用一下這個反射(當然,是用反射去解釋一個東西,反射是java的核心之一吧,不僅僅是技術層面,在思想層面,反射也是很強大的)

Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。

先提出這個一個觀念,如果不相信,那么往后看:

看一下上面的演示代碼,然后,再看一下運行結果:

現在,不需要多說了吧。泛型,只是在編譯期,才有用的,到了運行時,就沒有用了。

那么再稍微多走幾步看看:現在,我們試著從這個集合里面拿出數據看看



看到結果了吧,拋異常了,int類型的數據不能放到string數據中。這就是為什么前面說:Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。這個防止錯誤輸入,就是在這里體現了。如果沒有泛型,我們可能會出現錯誤輸入,導致jvm崩掉。但是,由于反射機制的存在,我們卻可以繞過這個檢查,強制輸入錯誤數據。所以,反射雖好,但也要注意合理使用。

幫助理解

ok,java反射,基礎的東西差不多就是這些了。下面來到我們的應用部分,java注解,說java注解是java反射的應用或者是升華都是可以的。

JAVA注解

概念及作用

概念

注解即元數據,就是源代碼的元數據

注解在代碼中添加信息提供了一種形式化的方法,可以在后續中更方便的 使用這些數據

Annotation是一種應用于類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。

作用

生成文檔

跟蹤代碼依賴性,實現替代配置文件功能,減少配置。如Spring中的一些注解

在編譯時進行格式檢查,如@Override等

每當你創建描述符性質的類或者接口時,一旦其中包含重復性的工作,就可以考慮使用注解來簡化與自動化該過程。

什么是java注解?

在java語法中,使用@符號作為開頭,并在@后面緊跟注解名。被運用于類,接口,方法和字段之上,例如:

@Override

void myMethod(){

......

}

這其中@Override就是注解。這個注解的作用也就是告訴編譯器,myMethod()方法覆寫了父類中的myMethod()方法。

java中內置的注解

java中有三個內置的注解:

@Override:表示當前的方法定義將覆蓋超類中的方法,如果出現錯誤,編譯器就會報錯。

@Deprecated:如果使用此注解,編譯器會出現警告信息。

@SuppressWarnings:忽略編譯器的警告信息。

元注解

自定義注解的時候用到的,簡單說,就是注解的注解

元注解的作用就是負責注解其他注解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型作說明。

@Target

@Retention

@Documented

@Inherited

不過java8新增了兩個新注解(這里不講,原因是:我現在還沒用java8,還有就是,先學最基礎的吧,新注解后面再學)

@Target

@Target說明了Annotation所修飾的對象范圍:Annotation可被用于 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。

作用:用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)

取值(ElementType)有:


比如說這個注解表示只能在方法中使用:

先定義一個注解

然后再使用

@Retention

@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意并不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命周期”限制。

作用:表示需要在什么級別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內有效)

取值(RetentionPoicy)有:


使用示例:



@Documented

@Documented用于描述其它類型的annotation應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記注解,沒有成員。

作用:將注解包含在javadoc中(這個就不舉例了,沒什么好說的)

@Inherited

是一個標記注解

闡述了某個被標注的類型是被繼承的

使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation將被用于該class的子類

@Inherited annotation類型是被標注過的class的子類所繼承。類并不從實現的接口繼承annotation,方法不從它所重載的方法繼承annotation

當@Inherited annotation類型標注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。

作用:允許子類繼承父類中的注解

還是一個例子:



看了這幾個例子,現在應該有點感覺了吧,注解,其實就是,先定義一個注解,然后,再使用。只是在定義注解的時候,要注意他的作用域和生命周期。

ok,下面重頭戲,前面都是介紹基礎,下面我們來自定義注解。

自定義注解

格式

public @interface 注解名{

定義體

}

注解參數的可支持數據類型:

所有基本數據類型(int,float,double,boolean,byte,char,long,short)

String 類型

Class類型

enum類型

Annotation類型

以上所有類型的數組

規則

修飾符只能是public 或默認(default)

參數成員只能用基本類型byte,short,int,long,float,double,boolean八種基本類型和String,Enum,Class,annotations及這些類型的數組

如果只有一個參數成員,最好將名稱設為”value”

注解元素必須有確定的值,可以在注解中定義默認值,也可以使用注解時指定,非基本類型的值不可為null,常使用空字符串或0作默認值

在表現一個元素存在或缺失的狀態時,定義一下特殊值來表示,如空字符串或負值

注解處理器類庫(java.lang.reflect.AnnotatedElement)

Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現類:

Class:類定義

Constructor:構造器定義

Field:類的成員變量定義

Method:類的方法定義

Package:類的包定義

java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義為運行時的Annotation后,該注解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation才會被虛擬機讀取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調用該對象的如下四個個方法來訪問Annotation信息:

方法1:T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。

方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有注解。

方法3:boolean is AnnotationPresent(Class annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.

方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。

下面來一個完整的demo解釋一下:

這里,用一個水果的例子說明一下:一個水果,有名字,顏色,產地等信息,這里簡單一點,就三個吧。

首先是自定義注解:



這個應該不用多解釋了吧,就是定義了名字,顏色和生產商的注解

然后就是使用注解(其實我更多的認為是實現注解,因為注解感覺是一個接口,個人認為哈)


然后是注解處理(說白了,其實就是具體操作,這里是用的反射)


ok,最后一步了,當然,就是跑起來看結果:



看到了吧,就是這么神奇。

ok,反射和注解的基礎,就先講到這里,以后自己水平提高了,再來聊聊這個高級應用。

最后附上一張思維導圖結束了


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容