Java 在運行時識別對象和類的信息轿衔,主要有兩種方式:一種是傳統(tǒng)的RTTI(Run-Time Type Identification),它假定在編譯時已經(jīng)知道了所有的類型信息斑匪;另一種是反射機制,它允許在運行時發(fā)現(xiàn)和使用類的信息疤剑。使用的前提條件:必須先得到目標類的字節(jié)碼的 Class蹬癌,Class 類用于表示.class文件(字節(jié)碼)
一、反射的概述
Java 反射機制是在運行狀態(tài)中镀梭,對于任意一個類刀森,都能夠知道這個類的所有屬性和方法;對于任意一個對象报账,都能夠調(diào)用它的任意一個方法和屬性研底。這種動態(tài)獲取信息以及動態(tài)調(diào)用對象的方法的功能稱為 Java 的反射機制。
要想解剖一個類透罢,必須先要獲取到該類的字節(jié)碼文件對象榜晦。而解剖使用的就是 Class 類中的方法。所以先要獲取到每一個字節(jié)碼文件對應的 Class 類型的對象羽圃。反射就是把 Java 類中的各種成分映射成一個個的 Java 對象乾胶。
二、反射的理解
什么是反射炼幔?反射就是在運行時才知道要操作的類是什么秋茫,并且可以在運行時獲取類的完整構造,并調(diào)用對應的方法乃秀。
一般肛著,使用某個類時必定知道它是什么類圆兵,是用來做什么的。于是直接對這個類進行實例化枢贿,之后使用這個類對象進行操作殉农。如:
Phone phone = new Phone(); //直接初始化「正射」
phone.setPrice(4);
如此進行類對象的初始化,可以理解為「正」局荚。而反射則是一開始并不知道要初始化的類對象是什么超凳,自然也無法使用 new 關鍵字來創(chuàng)建對象了。這時候耀态,使用 JDK 提供的反射 API 進行反射調(diào)用:
Class clz = Class.forName("com.xxp.reflect.Phone");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面兩段代碼的執(zhí)行結果完全一樣轮傍。但是思路完全不一樣,第一段代碼在未運行時就已經(jīng)確定了要運行的類(Phone)首装,而第二段代碼則是在運行時通過字符串值才得知要運行的類(com.xxp.reflect.Phone)创夜。完整代碼如下:
public class Phone {
private int price;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public static void main(String[] args) throws Exception{
//正常的調(diào)用
Phone phone = new Phone();
phone.setPrice(5000);
System.out.println("Phone Price:" + phone.getPrice());
//使用反射調(diào)用
Class clz = Class.forName("com.xxp.api.Phone");
Method setPriceMethod = clz.getMethod("setPrice", int.class);
Constructor phoneConstructor = clz.getConstructor();
Object phoneObj = phoneConstructor.newInstance();
setPriceMethod.invoke(phoneObj, 6000);
Method getPriceMethod = clz.getMethod("getPrice");
System.out.println("Phone Price:" + getPriceMethod.invoke(phoneObj));
}
}
代碼中使用反射調(diào)用了 setPrice(),并傳遞了 6000 的值仙逻。之后使用反射調(diào)用了 getPrice()驰吓,輸出其價格。輸出結果:
Phone Price:5000
Phone Price:6000
小結:一般使用反射獲取一個對象的步驟:
//獲取類的 Class 對象實例
Class clz = Class.forName("com.xxp.api.Phone");
//根據(jù) Class 對象實例獲取 Constructor 對象
Constructor phoneConstructor = clz.getConstructor();
//使用 Constructor 對象的 newInstance() 獲取反射類對象
Object phoneObj = phoneConstructor.newInstance();
如果要調(diào)用某一個方法桨醋,則需要經(jīng)過下面的步驟:
//獲取方法的 Method 對象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
//利用 invoke 調(diào)用方法
setPriceMethod.invoke(phoneObj, 6000);
三棚瘟、反射的常用API
在 JDK 中,反射相關的 API 可分為:獲取反射的 Class 對象喜最、通過反射創(chuàng)建類對象偎蘸、通過反射獲取類屬性方法及構造器。反射常用 API:
1??獲取反射中的 Class 對象
在反射中瞬内,要獲取一個類或調(diào)用一個類的方法迷雪,首先需要獲取到該類的 Class 對象。在 Java API 中虫蝶,獲取 Class 類對象有三種方法:
- 【調(diào)用 Class 的 Class.forName 靜態(tài)方法】當知道某類的全路徑名時章咧,可以使用此方法獲取 Class 類對象。用的最多能真,但可能拋出 ClassNotFoundException 異常赁严。
Class c1 = Class.forName(“java.lang.String”);
- 【調(diào)用運行時類的屬性
類名.class
】該方法最為安全可靠,程序性能更高粉铐。這說明任何一個類都有一個隱含的靜態(tài)成員變量 class疼约。這種方法只適合在編譯前就知道操作的 Class。
Class c2 = String.class;
- 【通過運行時類的對象調(diào)用 getClass()】通常應用在:比如傳過來一個 Object 類型的對象蝙泼,而不知道具體是什么類程剥,用這種方法。
String str = new String("Hello");
Class c3 = str.getClass();
- 【使用類的加載器 ClassLoader】(了解)
ClassLoader classLoader = Phone.class.getClassLoader();
Class c4 = classLoader.loadClass("com.xxp.api.Phone");
注意:一個類在 JVM 中只會有一個 Class 實例汤踏,即對上面獲取的c1织鲸、c2舔腾、c3和c4進行 equals 或者雙等于比較,發(fā)現(xiàn)都是 true搂擦。是單例的稳诚。
2??通過反射創(chuàng)建類對象
通過反射創(chuàng)建類對象主要有兩種方式:通過 Class 對象的 newInstance()、通過 Constructor 對象的 newInstance()盾饮。
- 通過 Class 對象的 newInstance()采桃。
Class clz = Phone.class;
Phone phone = (Phone)clz.newInstance();
- 通過 Constructor 對象的 newInstance()
Class clz = Phone.class;
Constructor constructor = clz.getConstructor();
Phone phone= (Phone)constructor.newInstance();
通過 Constructor 對象創(chuàng)建類對象可以選擇特定構造方法,而通過 Class 對象則只能使用默認的無參數(shù)構造方法丘损。下面的代碼就調(diào)用了一個有參數(shù)的構造方法進行了類對象的初始化普办。
Class clz = Phone.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Phone phone = (Phone)constructor.newInstance("華為",6666);
3??通過反射獲取類屬性、方法徘钥、構造器
通過 Class 對象的 getFields() 可以獲取 Class 類的屬性衔蹲,但無法獲取私有屬性。
Class clz = Phone.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
輸出結果是:
price
而如果使用 Class 對象的 getDeclaredFields() 則可以獲取包括私有屬性在內(nèi)的所有屬性:
Class clz = Phone.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
輸出結果是:
name
price
與獲取類屬性一樣呈础,當獲取類方法舆驶、類構造器時,如果要獲取私有方法或私有構造器而钞,則必須使用有 declared 關鍵字的方法沙廉。
附:查閱 API 可以看到 Class 有很多方法:
getName():獲得類的完整名字。
getFields():獲得類的public類型的屬性臼节。
getDeclaredFields():獲得類的所有屬性撬陵。包括private 聲明的和繼承類
getMethods():獲得類的public類型的方法鲫竞。
getDeclaredMethods():獲得類的所有方法擒权。包括private 聲明的和繼承類
getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數(shù)指定方法的名字培他,parameterTypes 參數(shù)指定方法的參數(shù)類型粉臊。
getConstructors():獲得類的public類型的構造方法草添。
getConstructor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes 參數(shù)指定構造方法的參數(shù)類型扼仲。
newInstance():通過類的不帶參數(shù)的構造方法創(chuàng)建這個類的一個對象远寸。
四、反射源碼解析
在 Web 項目中偶爾會遇到下面的異常:
java.lang.NullPointerException
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:369)
可以看到異常堆棧指出了異常在 Method 的第 369 的 invoke 方法中屠凶,其實這里指的 invoke 方法就是反射調(diào)用方法中的 invoke驰后。
Method method = clz.getMethod("setPrice", int.class);
method.invoke(object, 6); //就是這里的invoke方法
例如經(jīng)常使用的 Spring 配置中,經(jīng)常會有相關 Bean 的配置:
<bean class="com.xxp.Phone"></bean>
當在 XML 文件中配置了上面這段配置之后阅畴,Spring 便會在啟動的時候利用反射去加載對應的 Phone類倡怎。而當 Apple 類不存在或發(fā)生啟發(fā)異常時迅耘,異常堆棧便會將異常指向調(diào)用的 invoke 方法贱枣。由此可知监署,很多框架都使用了反射,而反射中最重要的就是 Method 類的 invoke 方法了纽哥。
五钠乏、反射總結
反射機制允許程序在運行時取得任何一個已知名稱的 class 的內(nèi)部信息,包括包括其 modifiers(修飾符)春塌,fields(屬性)晓避,methods(方法)等,并可于運行時改變 fields 內(nèi)容或調(diào)用 methods只壳。由此便可以更靈活的編寫代碼俏拱,代碼可以在運行時裝配,無需在組件之間進行源代碼鏈接吼句,降低代碼的耦合度锅必;還有動態(tài)代理的實現(xiàn);JDBC原生代碼注冊驅(qū)動惕艳;hibernate 的實體類搞隐;Spring的AOP 等等。但是凡事都有兩面性远搪,反射使用不當會造成很高的資源消耗劣纲。
六、new對象和反射得到對象的區(qū)別
- 在使用反射的時候谁鳍,必須確保這個類已經(jīng)加載并已經(jīng)連接了癞季。使用 new 的時候,這個類可以沒有被加載棠耕,也可以已經(jīng)被加載余佛。
- new 關鍵字可以調(diào)用任何 public 構造方法。反射可以調(diào)用特定構造方法窍荧,甚至私有方法辉巡。
- new 關鍵字是強類型的,效率相對較高蕊退。 反射是弱類型的郊楣,效率低。
- 反射提供了一種更加靈活的方式創(chuàng)建對象瓤荔,得到對象的信息净蚤。如 Spring 中 AOP 等的使用,動態(tài)代理的使用输硝,都是基于反射的今瀑。解耦。
七、哪些類型可以有Class對象橘荠?
// 哪些類型可以有Class對象屿附?
// 所有類型的Class
public class ClassDemo {
public static void main(String[] args) {
Class c1 = Object.class; //類
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一維數(shù)組
Class c4 = int[][].class; //二維數(shù)組
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚舉
Class c7 = Integer.class; //基本數(shù)據(jù)類型
Class c8 = void.class; //空類型
Class c9 = Class.class; // Class本身
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
//只要元素類型與維度一樣,就是同一個Class
//同一個元素同一個類只有一個Class對象
//一個類只有一個Class對象
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
}
}