剛開始接觸反射這個概念,感覺反射這個機制很復雜很難懂,所以在這篇文章中對java的反射機制以個人的理解總結歸納。
1. 什么是反射?
什么是反射?在官方文檔中是這樣說的:
Reflection is commonly used by programs which require the ability to examine ormodify the runtime behavior of applications running in the Java virtual machine.
This is a relatively advanced feature and should be used only by developers whohave a strong grasp of the fundamentals of the language.
With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible
翻譯一下:
反射技術通常被用來檢測和改變應用程序在 Java 虛擬機中的行為表現。它是一個相對而言比較高級的技術,通常它應用的前提是開發者本身對於 Java 語言特性有很強的理解的基礎上。值得說明的是,反射是一種強有力的技術特性,因此可以使得應用程序執行一些常規手段無法企及的目的。
個人理解:反射是一種很牛x的技術,使用反射的條件是程序猿是一個大猿,對java的特性非常理解。反射的牛逼之處在於他可以完成一些非常規操作。
舉個栗子來說明一下:
稍微想了一下,覺得用煮飯這個栗子來說明吧,不知道准不准確(^,^)。平時我們在家里一般是用電飯煲來煮飯的,煮飯的步驟一般是:淘米——>擦干鍋底——>把鍋放到電飯煲里——>合上蓋子,通電,電飯煲工作——>飯煮熟了,可以吃了,但現在有個需求,我要在煮飯的過程中加個雞蛋,這時怎么解決呢?是不是打開正在通電煮飯的電飯煲,然后把雞蛋放進去呢?(來自吃貨的需求^_^)其實反射就相當於剛才加雞蛋的過程。所以反射很牛逼,他不按常規套路出牌,在程序運行的過程中搞一些小動作,以達到“吃貨”的目的。補充一下:“淘米——>擦干鍋底——>把鍋放到電飯煲里——>”這個過程可以看做編碼編譯過程,“——>合上蓋子,通電,電飯煲工作”可以看做是程序運行過程,“——>飯煮熟了,可以吃了”可以看做程序運行結束。
2.java中的反射機制
2.1 反射中常見的類
理解反射機制時,首先熟悉一下幾個類:
1)Class類
Class類實例表示正在運行的Java應用程序中的類和接口。Class是普通類、接口、枚舉類、數組等的抽象,即它們的類型就是Class,它們是Class的實例。
既然Class代表着類和接口,那么我們可以通過他的實例(字節碼文件)來獲取對應類或接口的信息,如:注解、修飾符、類型、類的名稱、屬性、方法、構造方法、直接父類和子類等,還有可以創建它的實例,但只能調用無參構造方法來創建。
什么看不懂?舉個栗子,我們都知道,生物可以分為動物、植物、微生物和病毒等,而動物又有人、喵星人、小狗等,植物、微生物和病毒也一樣。同樣,我們可以類比一下,生物就是Class,動物是普通類,植物是接口,微生物是枚舉類、病毒是數組(枚舉和數組是特殊的類),而人、喵星人、小狗是我們熟悉的對象,如圖
這下可整明白了吧,普通類、接口、枚舉、數組其實都可以當做Class的對象。
2)Field類
Field表示類的屬性,屬性含有修飾符、類型、屬性名稱和值。所以可以通過Field的實例獲取屬性的修飾符、類型、屬性名稱,並且可以修改屬性的值。
3)Method類
Method表示類的成員方法,方法包括注解、修飾符、返回類型、方法名,參數等。所以可以通過Method的實例獲取方法的的信息,如,注解、修飾符、返回類型、方法名並且可以調用所表示的方法。
4)Constructor類
Constructor表示構造方法,可以通過Constructor的實例獲取構造方法的信息,如,修飾符等,並且可以通過它來創建它所在類的的實例。
5)Modifier類
Modifier表示修飾符,可通過它來獲取修飾符的信息,例如何種修飾符等
6)Annotation
Annotation代表注解
以上類都位於java.lang中
2.2 獲取Class對象的方法
了解了什么是反射后,是不是也想體驗一下反射這種騷操作?
想秀操作,首先要獲取Class對象吧,因為Class對象是代表着各種類,有了它之后才可以得到類的各種信息。獲取方法如下:
1)通過object.getClass()
1 public static void main(String[] args) { 2 3 Car car = new Car(); 4 5 Class clazz = car.getClass(); 6 }
注意:此方法不適用於int、float等類型
2)通過(類型名).class、包裝類.Type
1 public static void main(String[] args) { 2 Class clazz = Car.class; 3 Class cls1 = int.class; 4 Class cls2 = String.class; 5 Class cls3=Iteger.Type 6 }
3)通過Class.forClass(String 類的全限定名)
1 try { 2 Class clz = Class.forName("com.frank.test.Car"); 3 } catch (ClassNotFoundException e) { 4 e.printStackTrace(); 5 }
采 用哪種方法來獲取,看實際情況而定。
2.3獲取類信息
有了Class對象后,就可以獲取類的成員(方法+屬性)、注解和類的修飾符等。上面也說了,java中方法用Method類表示、屬性用Field類表示、注解用Annotation類來表示、修飾符用Modifier類表示。Class類中有對應的方法來獲取他們。如下:
2.3.1 獲取屬性Field的對象
1 //獲取所有的屬性,但不包括從父類繼承下來的屬性 2 public Field[] getDeclaredFields() throws SecurityException 3 //獲取自身的所有的 public 屬性,包括從父類繼承下來的。 4 public Field[] getFields() throws SecurityException
5 //獲取在本類中聲明的指定的屬性,參數為屬性的名稱 6 public Field getDeclaredField(String name) 7 //獲取指定的公有屬性,包括父類的,參數為屬性的名稱
8 public Field getField(String name)
2.3.2 獲取方法Method對象
//獲取本類聲明指定的的方法,第一個參數是方法的名稱,后面的參數是方法參數類型的類,
//如獲取setName(String name)方法,getDeclareMethod(“setName”,String.Class) public Method getDeclaredMethod(String name, Class<?>... parameterTypes) //獲取公有的方法,包括父類的 public Method getMethod(String name, Class<?>... parameterTypes) //獲取本類中聲明的所有方法 public Method[] getDeclaredMethods() //獲取所有的公有方法,包括父類的 public Method[] getMethods()
2.3.3 獲取構造器Constructor對象
1 //獲取本類中指定的構造方法 2 public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 3 //獲取指定的公有構造方法 4 public Constructor<T> getConstructor(Class<?>... parameterTypes) 5 //獲取本類中所有的構造方法 6 public Constructor<?>[] getDeclaredConstructors() throws SecurityException 7 //獲取本類中所有的公有構造方法 8 public Constructor<?>[] getConstructors()
構造方法的獲取與普通方法的獲取大致是一樣的。
------------------------------------------------------------------
以上的方法都是在Class類中,別傻傻不知道(別問我怎么知道的>_>),然后通過Class對象調用就可以了。
這里只是列舉了常用類信息的的獲取方法,其他信息的獲取方法,看API文檔吧,如注解、類的Class的對象(額好像有點繞。。。)等.
2.4 獲取類成員信息
上面只是獲取了類的成員所代表類的對象,我們還要使用他們或者獲取成員的信息(名稱、修飾符等)。因為有了代表成員的對象,使用對象調用實例方法就可以了。
2.4.1 Field類
Field類的方法大概可以分為兩種,一種是獲取屬性的信息,另外一種是設置屬性的值。
第一種:
1 //返回由此 Field對象表示的字段的名稱 2 String getName() 3 //返回一個 類對象標識了此表示的字段的聲明類型 Field對象。 4 Class<?> getType() 5 //返回由該 Field對象表示的字段的Java語言修飾符,作為整數。把整數作為Modifier的構造方法的參數,就可以獲取該整數代表的修飾符類的對象了 6 int getModifiers() 7 ---------------------------------------------------------------- 8 9 //獲取類型為 int的靜態或實例字段的值,或通過擴展轉換轉換為類型 int的另一個原始類型的值。 10 int getInt(Object obj) 11 //獲取類型為 long的靜態或實例字段的值,或通過擴大轉換獲得可轉換為類型 long的另一個基本類型的值。 12 long getLong(Object obj) 13 ......此處省略一堆get**(Object obj)的方法,屬性是什么基本類型,就get什么就行了 14屬性是引用類型,那么就調用以下方法
15 //返回該所表示的字段的 Field ,指定的對象上。 16 Object get(Object obj)
第二種:
1 //設置作為一個字段的值 double指定的對象上。 2 void setDouble(Object obj, double d) 3 //設置作為一個字段的值 float指定的對象上。 4 void setFloat(Object obj, float f) 5 //設置作為一個字段的值 int指定的對象上。 6 void setInt(Object obj, int i) 7 ........此處省略一堆set**()方法,屬性是什么基本類型就set什么就行了 8 屬性是引用類型,那么就調用以下方法 9 //將指定對象參數上的此 Field對象表示的字段設置為指定的新值。 10 void set(Object obj, Object value)
注意啦:如果沒有訪問權限的話,默認是不能設置屬性值的,那怎么辦呢?是不是就秀不了操作了?然而,前面也說了,反射很牛逼,可以來一些非常規操作,
這時我們調用Class對象的setAccessible(true)方法就可以了!
是不是覺得反射可以很強?
2.4.1 Method類
Method類的方法主要是獲取方法的信息
部分方法:
1 int getModifiers() //返回由該對象表示的可執行文件的Java語言modifiers 。 2 String getName() //返回由此 方法對象表示的方法的名稱,作為 String 。 3 Annotation[][] getParameterAnnotations() //返回一個 Annotation s的數組數組,表示由該對象表示的Executable的形式參數的聲明順序的 Executable 。 4 int getParameterCount() //返回由此對象表示的可執行文件的形式參數(無論是顯式聲明還是隱式聲明)的數量。 5 Class<?>[] getParameterTypes() //返回一個 類對象的數組, 類以聲明順序表示由該對象表示的可執行文件的形式參數類型。
2.4.1 Constructor類
Constructor類的方法主要是獲取構方法的信息和創建對象
獲取方法信息:
1 int getModifiers() //返回由該對象表示的可執行文件的Java語言modifiers 。 2 String getName() //以字符串形式返回此構造函數的名稱。 3 Annotation[][] getParameterAnnotations() //返回的數組的數組 Annotation表示的形參進行注釋s時,聲明順序的的 Executable該對象表示。 4 int getParameterCount() //返回由此對象表示的可執行文件的形式參數(無論是顯式聲明還是隱式聲明)的數量。 5 Class<?>[] getParameterTypes() //返回一個 類對象的數組, 類以聲明順序表示由該對象表示的可執行文件的形式參數類型。
創建對象的方法先不說,放到后面去。
2.5 反射創建對象和調用方法
2.5.1 創建普通類的對象
創建普通類的對象可以分為兩種方法
第一種:調用Class對象的方法
4 //首先獲取Class對象 5 Class clazz=Class.forClass("test.Student"); 6 //創建對象 7 Student stu=(Student)clazz.newInstance();
注:此方法只能創建無參構造函數的類的對象
第二種:通過Constructor的newInstance()方法
1 //首先創建Class對象 2 Class clazz=Class.forClass("test.Student"); 3 //獲取想調用的構造函數 4 Constructor constructor=clazz.getConstructor(String.class, int.class); 5 //調用Constructor的newInstance()方法 6 Student stu=(Student)constructor.newInstance("大王",20);
2.5.2 創建數組
數組本質上是一個 Class,而在 Class 中存在一個方法用來識別它是否為一個數組。
反射創建數組是通過 Array.newInstance(T.class,維數) 這個方法。
第一個參數指定的是數組內的元素類型,后面的是可變參數,表示的是相應維度的數組長度限制。
比如,我要創建一個 int[2][3] 的數組。
1 Int[][] a=Array.newInstance(Integer.TYPE, 2, 3);
2.5.3 調用方法
用了上面的方法,就有Class對象,有方法Method對象,有實例,現在已經萬事俱備,只欠東風了。
那我們怎么調用方法呢?在Method類有這么一個方法Object invoke(Object obj, Object... args),object為實例對象,args為調用方法的參數
來個栗子:
1 Class<?> c = Class.forName("com.kal01.reflect05.Person");//獲取Class對象 2 Person p1 = (Person) c.newInstance();//獲取實例 3 Method m3 = c.getDeclaredMethod("test");//獲取方法 4 m3.setAccessible(true);//當沒有訪問權限時,設置一下就可以 5 m3.invoke(p1);//調用方法 6 m3.setAccessible(false);//修改了訪問權限,記得修改回來
2.6 靜態加載與動態加載
看到這里是不是有個疑問,反射調用類的方法好像除了復雜之外,跟我們平時調用沒什么區別。何必弄那么花里胡哨?
所以在這里簡單說一下靜態加載與動態加載。
回想一下之前煮飯的那個栗子,靜態加載和動態加載的這個例子有點相似。
靜態加載:我們在程序中使用類時,靜態加載是要求要使用的類必須要求在編譯的時候存在,否則編譯器報錯,無法運行程序。編碼時忘記導包時,經常會出現這種錯誤。
動態加載:利用反射來加載類(即獲得Class對象),不要求我們在編譯期存在要是用的那個類,在程序運行時,才去尋找類(可以從jar包,網絡等尋找),然后把類加載到方法區中,如果沒有找到這個類會拋出ClassNotFoundException異常。
有圖有真相:
這下看出來反射的牛逼之處了吧,利用反射使用類時,並不需要這個類在編譯期存在,這就增加了程序的靈活性,可以完成我們的騷操作!
最后總結一下:
使用反射時,記住一句話:老哥,穩住,別翻車!