反射是Java中一個非常重要、非常強大的機制。曾看到一句話“反射是框架的靈魂”,初學時不懂,等到學完框架之后才慢慢理解其意。
什么是反射?我們先通過幾個類和示例來初步體會一下反射。
一、ClassLoader類
什么是類加載器?
ClassLoader是一個抽象類,它的實例是類加載器。磁盤上存在的xxx.class文件需要被加載進JVM才能執行。類加載器則是負責加載.class文件的對象,然后在JVM中生成該類的Class對象。每一個Class對象都關聯着定義它的那個類加載器。數組的類加載器與其元素的加載器是同一個,如果元素類型是基本類型,則數組沒有類加載器。
類加載器工作原理
類加載器都有一個與之關聯的父加載器,當加載器需要加載一個文件時,它首先將該任務”委派”給父加載器,如果父加載器無法加載該文件,再自己進行加載。JVM的引導加載器(bootstrap class loader)沒有父加載器,但可作為父加載器。關於類加載器更詳細的分析 點擊這里
二、Class類
什么是Class<T>類?
Class不是我們平常聲明類時所用的關鍵字class,它是一個類,它的對象用來描述一個運行狀態的類或接口。一個xxx.java文件編譯后生成一個xxx.class文件,一個xxx.class文件被JVM加載后生成該類對應的Class對象,該對象包含了該類的所有信息,比如,類中有字段、構造器、方法等信息。一個類有一個對應的Class對象,元素類型相同且長度相同的數組共享一個Class對象,java基本類型包括void也都有各自的Class對象。Class是一個泛型類,如果不加泛型,需要強轉。
如何獲取一個類的Class對象?
Class沒有public構造器,當類被加載時,JVM會通過調用ClassLoader的defineClass方法來自動創建該類的Class對象。
獲取一個類的Class對象有三種方式:
1)類名.class
2) 該類的對象.getClass()
3) Class.forName(String 類名) (包名加類名)
Class對象有何作用?
下面列出幾個Class類的方法:
1) 獲取類加載器:
getClassLoader()
2) 獲取Constructor構造器對象:
getConstructor(Class... parameterType) 獲取具有指定參數的公共構造器對象
getConstructors() 獲取所有公共構造器對象
getDeclaredConstructor(Class... parameterType) 獲取具有指定參數的構造器對象
getDdclaredConstructors() 獲取所有構造器對象
3) 獲取Method對象:
getMethod(String name,Class...parameterType) 獲取具有指定名稱和參數的公共方法對象
getMethods() 獲取所有公共方法對象
getDeclaredMethod(String name,Class...parameterType) 獲取具有指定名稱和參數的方法對象
getDeclaredMethods() 獲取所有的方法對象
4)獲取Field字段對象:
getField(String name) 獲取具有指定名稱的公共字段對象
getFields() 獲取具有所有公共字段對象
getDeclaredField(String name) 獲取具有指定名稱的字段對象
getDeclaredFields() 獲取所有字段對象
5)獲取Class對象所代表的類的一個對象(非常重要的一個方法)
Object newInstance() 用默認的無參數構造器創建一個對象
(帶Declared的get方法可以獲取任意訪問權限的成員,不帶Declared的只能獲取public成員)
測試Class,更多的測試在其他類的測試中體現。
package cn.edu; public class User { private String username; private int age; public User() { } public User(String username , int age) { this.username = username; this.age = age; } private User(String username) { this.username = username; }
public void say(String str) {
System.out.println(str);
}
@Override public String toString() { return "User [username=" + username + ", age=" + age + "]"; } }
@Test public void fun2() throws Exception { Class userClass = User.class; System.out.println(userClass.getName()); //獲取該class對象的類的類名 System.out.println(userClass.getClassLoader().toString()); //獲取類加載器 User user = (User)userClass.newInstance(); //調用默認的無參數構造器創建對象 System.out.println(user.toString()); }
運行結果:
三、Constructor類
構造器類,封裝構造器的有關信息。
主要方法 Object newInstance(Object...arg) 用指定參數創建對象
測試Constructor
@Test public void fun2() throws Exception { Class userClass = User.class; Constructor userConstructor1 = userClass.getConstructor(String.class , int.class); //有參構造器 User user1 = (User)userConstructor1.newInstance("小紅",18); System.out.println(user1.toString()); Constructor userConstructor2 = userClass.getConstructor(); //無參構造器 User user2 = (User)userConstructor2.newInstance(); System.out.println(user2); Constructor userConstructor3 = userClass.getDeclaredConstructor(String.class); //私有構造器 userConstructor3.setAccessible(true); //開啟訪問權限 User user3 = (User)userConstructor3.newInstance("小明"); System.out.println(user3); }
運行結果:
默認是不可以訪問private成員的,userConstructor3.setAccessible(true)是用於開啟訪問權限的,這樣就可以訪問了,感覺是開掛一樣!
四、Method類
方法類,封裝方法的有關信息
主要方法
Object invoke(Object obj , Object... args) 調用obj對象的Method對象代表的方法,args為參數
測試Method:
@Test public void fun2() throws Exception { Class userClass = User.class; User user = (User)userClass.newInstance(); //用默認無參數構造方法創建對象 Method method = userClass.getMethod("say",String.class); //獲取名為"say",參數為string的method對象 method.invoke(user,"hello"); //調用user的say方法 }
運行結果:
五、Field類
字段類,封裝字段的有關信息
主要方法
Object get(Object obj) 獲取obj對象的此Field對象代表的字段的值
void set(Object obj , Object value) 設置obj對象的此Field對象代表的字段的值
測試Fidle
@Test public void fun2() throws Exception { Class userClass = User.class; Constructor userConstructor = userClass.getConstructor(String.class,int.class); User user = (User)userConstructor.newInstance("小明",18); System.out.println(user.toString()); Field userField = userClass.getDeclaredField("username"); //獲取username字段 userField.setAccessible(true); //開啟訪問權限 userField.set(user, "小紅"); //給user對象的該字段設置值 System.out.println(user.toString()); }
運行結果:
到此關於反射的幾個類就簡單的認識了一下,我們可以不用new關鍵字來創建對象,調用對象的方法也與傳統的調用方式有很大區別,我們甚至可以操作private成員(雖然這樣做破壞了封裝性),相對於傳統的操作方式,反射更像是一種逆向思維,以前操作成員,主體在於對象,而反射的主體在於Class和成員本身。到此我們對反射有了初步的認識。接下在敘述一個重要的概念,有助於我們更好的理解反射。
六、動態加載與靜態加載
注意此處所說的加載是針對編譯的,將xxx.java轉化成xxx.class,而不是運行的加載字節碼。Java創建對象的常用方式是使用new 關鍵字,如 User user = new User(); 這種是靜態加載,即在編譯期就已經獲取User類的有關信息,如果User類不存在或有錯誤,編譯會報錯。動態加載就是用上面的Class.forName("包名.User");來加載User類,如果User類不存在或是有錯誤,在編譯時是不會報錯的,因為根本沒去加載User類。只有當程序運行到該處,JVM才會去加載User,而動態加載則是依賴反射實現的。
好了,可以給出最終概念了,通過上面的重重示例與解析,應該不難理解反射了。
七、反射
Java反射機制是指java程序在運行過程中,可以獲取任意一個類的相關信息,並且能夠調用其方法和屬性,這種動態獲取信息和動態調用方法的功能叫做Java的反射機制。
上面寫了這么多,總結起來也就這一句話。
本文個人編寫,水平有限,如有錯誤,懇請指出,歡迎討論分享。