- 反射機(jī)制的功能
Java反射機(jī)制主要提供了以下功能:
在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬的類冰蘑;
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象;
在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法村缸;
在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法祠肥;
生成動(dòng)態(tài)代理。
- 反射機(jī)制的利弊
其實(shí)好處就是梯皿,增加程序的靈活性,避免將程序?qū)懰赖酱a里仇箱;但是壞處也有,就是性能是一個(gè)問題东羹,反射相當(dāng)于一系列解釋操作剂桥,通知jvm要做的事情,性能比直接的java代碼要慢很多属提。且不安全权逗,通過反射機(jī)制我們能拿到類的私有成員。
- 詳解
Reflection垒拢。這個(gè)字的意思是“反射旬迹、映象、倒影”求类,用在Java身上指的是我們可以于運(yùn)行時(shí)加載奔垦、探知、使用編譯期間完全未知的classes尸疆。換句話說椿猎,Java程序可以加載一個(gè)運(yùn)行時(shí)才得知名稱的class,獲悉其完整構(gòu)造(但不包括methods定義)寿弱,并生成其對(duì)象實(shí)體犯眠、或?qū)ζ鋐ields設(shè)值、或喚起其methods症革。這種“看透class”的能力(the ability of the program to examine itself)被稱為introspection(內(nèi)省筐咧、內(nèi)觀、反省)量蕊。Reflection和introspection是常被并提的兩個(gè)術(shù)語铺罢。
反射之中包含了一個(gè)“反”的概念,所以要想解釋反射就必須先從“正”開始解釋残炮,一般而言韭赘,當(dāng)用戶使用一個(gè)類的時(shí)候,應(yīng)該先知道這個(gè)類势就,而后通過這個(gè)類產(chǎn)生實(shí)例化對(duì)象泉瞻,但是“反”指的是通過對(duì)象找到類。
packagecn.mldn.demo;
classPerson {
}
Public class TestDemo {
Public static void main(String[] args) throwsException {
Person per = newPerson() ; // 正著操作
System.out.println(per.getClass().getName()); // 反著來
}
}
以上的代碼使用了一個(gè)getClass()方法苞冯,而后就可以得到對(duì)象所在的“包.類”名稱袖牙,這就屬于“反”了,但是在這個(gè)“反”的操作之中有一個(gè)getClass()就作為發(fā)起一切反射操作的開端舅锄。
Person的父類是Object類贼陶,而上面所使用getClass()方法就是Object類之中所定義的方法。
·取得Class對(duì)象:public final Class<?> getClass()巧娱,反射之中的所有泛型都定義為?碉怔,返回值都是Object。
而這個(gè)getClass()方法返回的對(duì)象是Class類的對(duì)象禁添,所以這個(gè)Class就是所有反射操作的源頭撮胧。但是在講解其真正使用之前還有一個(gè)需要先解釋的問題,既然Class是所有反射操作的源頭老翘,那么這個(gè)類肯定是最為重要的芹啥,而如果要想取得這個(gè)類的實(shí)例化對(duì)象,Java中定義了三種方式:
方式一:通過Object類的getClass()方法取得铺峭,基本不用:
packagecn.mldn.demo;classPerson {
publicclassTestDemo {publicstaticvoidmain(String[] args) throwsException {
Person per = newPerson() ; // 正著操作
Class<?> cls = per.getClass() ; // 取得Class對(duì)象
System.out.println(cls.getName()); // 反著來
}
}
方式二:使用“類.class”取得墓怀,在日后學(xué)習(xí)Hibernate開發(fā)的時(shí)候使用
packagecn.mldn.demo;
classPerson {}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Person.class; // 取得Class對(duì)象
System.out.println(cls.getName()); // 反著來
}
}
方式三:使用Class類內(nèi)部定義的一個(gè)static方法,主要使用
·取得Class類對(duì)象:public static Class<?> forName(String className) throws ClassNotFoundException卫键;
packagecn.mldn.demo;
classPerson {}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
System.out.println(cls.getName()); // 反著來
}
}
那么現(xiàn)在一個(gè)新的問題又來了傀履,取得了Class類的對(duì)象有什么用處呢?對(duì)于對(duì)象的實(shí)例化操作之前一直依靠構(gòu)造方法和關(guān)鍵字new完成莉炉,可是有了Class類對(duì)象之后钓账,現(xiàn)在又提供了另外一種對(duì)象的實(shí)例化方法:
·通過反射實(shí)例化對(duì)象:public T newInstance() throws InstantiationException, IllegalAccessException;
范例:通過反射實(shí)例化對(duì)象
packagecn.mldn.demo;
classPerson {
@Override
publicString toString() {
return"Person Class Instance .";
}
}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
Object obj = cls.newInstance() ; // 實(shí)例化對(duì)象絮宁,和使用關(guān)鍵字new一樣
Person per = (Person) obj ; // 向下轉(zhuǎn)型
System.out.println(per);
}
}
那么現(xiàn)在可以發(fā)現(xiàn)梆暮,對(duì)于對(duì)象的實(shí)例化操作,除了使用關(guān)鍵字new之外又多了一個(gè)反射機(jī)制操作绍昂,而且這個(gè)操作要比之前使用的new復(fù)雜一些啦粹,可是有什么用偿荷?
對(duì)于程序的開發(fā)模式之前一直強(qiáng)調(diào):盡量減少耦合,而減少耦合的最好做法是使用接口唠椭,但是就算使用了接口也逃不出關(guān)鍵字new遭顶,所以實(shí)際上new是造成耦合的關(guān)鍵元兇。
范例:回顧一下之前所編寫的工廠設(shè)計(jì)模式
packagecn.mldn.demo;
interfaceFruit {
publicvoideat() ;
}
classApple implementsFruit {
publicvoideat() {
System.out.println("吃蘋果泪蔫。");
};
}
classFactory {
public static Fruit getInstance(String className) {
if("apple".equals(className)){
return new Apple() ;
}
returnnull;
}
}
publicclassFactoryDemo {
publicstaticvoidmain(String[] args) {
Fruit f = Factory.getInstance("apple") ;
f.eat() ;
}
}
以上為之前所編寫最簡(jiǎn)單的工廠設(shè)計(jì)模式,但是在這個(gè)工廠設(shè)計(jì)模式之中有一個(gè)最大的問題:如果現(xiàn)在接口的子類增加了喘批,那么工廠類肯定需要修改撩荣,這是它所面臨的最大問題,而這個(gè)最大問題造成的關(guān)鍵性的病因是new饶深,那么如果說現(xiàn)在不使用關(guān)鍵字new了餐曹,變?yōu)榱朔瓷錂C(jī)制呢?
反射機(jī)制實(shí)例化對(duì)象的時(shí)候?qū)嶋H上只需要“包.類”就可以敌厘,于是根據(jù)此操作台猴,修改工廠設(shè)計(jì)模式。
packagecn.mldn.demo;
interfaceFruit {
publicvoideat() ;
}
classApple implementsFruit {
publicvoideat() {
System.out.println("吃蘋果俱两。");
};
}
classOrange implementsFruit {
publicvoideat() {
System.out.println("吃橘子饱狂。");
};
}
classFactory {
publicstaticFruit getInstance(String className) {
Fruit f = null;
try{
f = (Fruit) Class.forName(className).newInstance() ;
} catch(Exception e) {
e.printStackTrace();
}
returnf ;
}
}
publicclassFactoryDemo {
publicstaticvoidmain(String[] args) {
Fruit f = Factory.getInstance("cn.mldn.demo.Orange") ;
f.eat() ;
}
}
發(fā)現(xiàn),這個(gè)時(shí)候即使增加了接口的子類宪彩,工廠類照樣可以完成對(duì)象的實(shí)例化操作休讳,這個(gè)才是真正的工廠類,可以應(yīng)對(duì)于所有的變化尿孔。如果單獨(dú)從開發(fā)角度而言俊柔,與開發(fā)者關(guān)系不大,但是對(duì)于日后學(xué)習(xí)的一些框架技術(shù)這個(gè)就是它實(shí)現(xiàn)的命脈活合,在日后的程序開發(fā)上雏婶,如果發(fā)現(xiàn)操作的過程之中需要傳遞了一個(gè)完整的“包.類”名稱的時(shí)候幾乎都是反射機(jī)制作用。
3.12.2 白指、反射的深入應(yīng)用
以上只是利用了Class類作為了反射實(shí)例化對(duì)象的基本應(yīng)用留晚,但是對(duì)于一個(gè)實(shí)例化對(duì)象而言,它需要調(diào)用類之中的構(gòu)造方法告嘲、普通方法倔丈、屬性,而這些操作都可以通過反射機(jī)制完成状蜗。
3.12.2 .1需五、調(diào)用構(gòu)造
使用反射機(jī)制也可以取得類之中的構(gòu)造方法,這個(gè)方法在Class類之中已經(jīng)明確定義了:
以下兩個(gè)方法
取得一個(gè)類的全部構(gòu)造:
public Constructor<?>[] getConstructors() throws SecurityException
取得一個(gè)類的指定參數(shù)構(gòu)造:
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
現(xiàn)在發(fā)現(xiàn)以上的兩個(gè)方法返回的都是java.lang.reflect.Constructor類的對(duì)象轧坎。
范例:取得一個(gè)類之中的全部構(gòu)造
packagecn.mldn.demo;
importjava.lang.reflect.Constructor;
classPerson { // CTRL + K
publicPerson() {}
publicPerson(String name) {}
publicPerson(String name,intage) {}
}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
Constructor<?> cons [] = cls.getConstructors() ; // 取得全部構(gòu)造
for(intx = 0; x < cons.length; x++) {
System.out.println(cons[x]);
}
}
}
驗(yàn)證:在之前強(qiáng)調(diào)的一個(gè)簡(jiǎn)單Java類必須存在一個(gè)無參構(gòu)造方法
范例:觀察沒有無參構(gòu)造的情況
packagecn.mldn.demo;
classPerson { // CTRL + K
privateString name;
privateintage;
publicPerson(String name,intage) {
this.name= name ;
this.age= age ;
}
@Override
publicString toString() {
return"Person [name="+ name+ ", age="+ age+ "]";
}
}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
Object obj = cls.newInstance(); // 實(shí)例化對(duì)象
System.out.println(obj);
}
}
此時(shí)程序運(yùn)行的時(shí)候出現(xiàn)了錯(cuò)誤提示“java.lang.InstantiationException”宏邮,因?yàn)橐陨系姆绞绞褂梅瓷鋵?shí)例化對(duì)象時(shí)需要的是類之中要提供無參構(gòu)造方法,但是現(xiàn)在既然沒有了無參構(gòu)造方法,那么就必須明確的找到一個(gè)構(gòu)造方法蜜氨,而后利用Constructor類之中的新方法實(shí)例化對(duì)象:
·實(shí)例化對(duì)象:public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException
packagecn.mldn.demo;
importjava.lang.reflect.Constructor;
classPerson { // CTRL + K
privateString name;
privateintage;
publicPerson(String name,intage) {
this.name= name ;
this.age= age ;
}
@Override
publicString toString() {
return"Person [name="+ name+ ", age="+ age+ "]";
}
}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
// 取得指定參數(shù)類型的構(gòu)造方法
Constructor<?> cons = cls.getConstructor(String.class,int.class) ;
Object obj = cons.newInstance("張三", 20); // 為構(gòu)造方法傳遞參數(shù)
System.out.println(obj);
}
}
很明顯械筛,調(diào)用無參構(gòu)造方法實(shí)例化對(duì)象要比調(diào)用有參構(gòu)造的更加簡(jiǎn)單、方便飒炎,所以在日后的所有開發(fā)之中埋哟,凡是有簡(jiǎn)單Java類出現(xiàn)的地方,都一定要提供無參構(gòu)造郎汪。
3.12.2 .2赤赊、調(diào)用普通方法
當(dāng)取得了一個(gè)類實(shí)例化對(duì)象之后,下面最需要調(diào)用的肯定是類之中的方法煞赢,所以可以繼續(xù)使用Class類取得一個(gè)類中所定義的方法定義:
·取得全部方法:public Method[] getMethods() throws SecurityException抛计;
·取得指定方法:public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
發(fā)現(xiàn)以上的方法返回的都是java.lang.reflect.Method類的對(duì)象。
范例:取得一個(gè)類之中所定義的全部方法
packagecn.mldn.demo;
importjava.lang.reflect.Method;
classPerson {
privateString name;
publicvoidsetName(String name) {
this.name= name;
}
publicString getName() {
returnname;
}
}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
Method met [] = cls.getMethods() ; // 取得全部方法
for(intx = 0; x < met.length; x++) {
System.out.println(met[x]);
}
}
}
但是取得了Method類對(duì)象最大的作用不再于方法的列出(方法的列出都在開發(fā)工具上使用了)照筑,但是對(duì)于取得了Method類對(duì)象之后還有一個(gè)最大的功能吹截,就是可以利用反射調(diào)用類中的方法:
·調(diào)用方法:public Object invoke(Object obj, Object... args) throws IllegalAccessException,IllegalArgumentException, InvocationTargetException
之前調(diào)用類中方法的時(shí)候使用的都是“對(duì)象.方法”锋谐,但是現(xiàn)在有了反射之后器躏,可以直接利用Object類調(diào)用指定子類的操作方法。(同時(shí)解釋一下缆娃,為什么setter蛾默、getter方法的命名要求如此嚴(yán)格)弟断。
范例:利用反射調(diào)用Person類之中的setName()、getName()方法
packagecn.mldn.demo;
importjava.lang.reflect.Method;
classPerson {
privateString name;
publicvoidsetName(String name) {
this.name= name;
}
publicString getName() {
returnname;
}
}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
Object obj = cls.newInstance(); // 實(shí)例化對(duì)象趴生,沒有向Person轉(zhuǎn)型
String attribute = "name"; // 要調(diào)用類之中的屬性
Method setMet = cls.getMethod("set"+ initcap(attribute), String.class);// setName()
Method getMet = cls.getMethod("get"+ initcap(attribute));// getName()
setMet.invoke(obj, "張三") ; // 等價(jià)于:Person對(duì)象.setName("張三")
System.out.println(getMet.invoke(obj));// 等價(jià)于:Person對(duì)象.getName()
}
publicstaticString initcap(String str) {
returnstr.substring(0,1).toUpperCase().concat(str.substring(1)) ;
}
}
在日后的所有框架技術(shù)開發(fā)之中阀趴,簡(jiǎn)單Java類都是如此應(yīng)用的,所以必須按照標(biāo)準(zhǔn)進(jìn)行苍匆。
3.12.2 .3刘急、調(diào)用成員
類之中最后一個(gè)組成部分就是成員(Field,也可以稱為屬性)浸踩,如果要通過反射取得類的成員可以使用方法如下:
·取得本類的全部成員:public Field[] getDeclaredFields() throws SecurityException叔汁;
·取得指定的成員:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;
這兩個(gè)方法的返回值類型是java.lang.reflect.Field類的對(duì)象检碗,下面首先觀察如何取得一個(gè)類之中的全部屬性据块。
范例:取得一個(gè)類之中的全部屬性
packagecn.mldn.demo;
importjava.lang.reflect.Field;
classPerson {
privateString name;
}
publicclassTestDemo {
publicstaticvoidmain(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對(duì)象
Field field [] = cls.getDeclaredFields() ; // 取得全部屬性
for(intx = 0; x < field.length; x++) {
System.out.println(field[x]);
}
}
}
但是找到Field實(shí)際上就找到了一個(gè)很有意思的操作,在Field類之中提供了兩個(gè)方法:
·設(shè)置屬性內(nèi)容(類似于:對(duì)象.屬性= 內(nèi)容):public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException折剃;
·取得屬性內(nèi)容(類似于:對(duì)象.屬性):public Object get(Object obj)
throws IllegalArgumentException, IllegalAccessException
可是從類的開發(fā)要求而言另假,一直都強(qiáng)調(diào)類之中的屬性必須封裝,所以現(xiàn)在調(diào)用之前要想辦法解除封裝怕犁。
·解除封裝:public void setAccessible(boolean flag) throws SecurityException边篮;
范例:利用反射操作類中的屬性
packagecn.mldn.demo;
importjava.lang.reflect.Field;
classPerson {
privateString name;
}
publicclassTestDemo {
public static void main(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對(duì)象
Object obj = cls.newInstance(); // 對(duì)象實(shí)例化屬性才會(huì)分配空間
Field nameField = cls.getDeclaredField("name") ; // 找到name屬性
nameField.setAccessible(true) ; // 解除封裝了
nameField.set(obj, "張三") ; // Person對(duì)象.name = "張三"
System.out.println(nameField.get(obj)); // Person對(duì)象.name
}
}