1、什麼是Java的反射java
你們都知道,要讓Java程序可以運行,那麼就得讓Java類要被Java虛擬機加載。Java類若是不被Java虛擬機加載,是不能正常運行的。如今咱們運行的全部的程序都是在編譯期的時候就已經知道了你所須要的那個類的已經被加載了。mysql
Java的反射機制是在編譯並不肯定是哪一個類被加載了,而是在程序運行的時候才加載、探知、自審。使用在編譯期並不知道的類。這樣的特色就是反射。sql
2、反射機制的做用設計模式
在Java運行時環境中,對於任意一個類,能夠知道這個類有哪些屬性和方法。對於任意一個對象,能夠調用它的任意一個方法。這種動態獲取類的信息以及動態調用對象的方法的功能來自於Java 語言的反射(Reflection)機制。簡單的來講,反射機制指的是程序在運行時可以獲取自身的信息。在java中,只要給定類的名字,那麼就能夠經過反射機制來得到類的全部信息。安全
3、哪裏用到反射機制app
有些時候,咱們用過一些知識,可是並不知道它的專業術語是什麼,在剛剛學jdbc時用過一行代碼,Class.forName("com.mysql.jdbc.Driver.class").newInstance();可是那時候只知道那行代碼是生成驅動對象實例,並不知道它的具體含義。聽了反射機制這節課後,才知道,原來這就是反射,如今不少開框架都用到反射機制,hibernate、struts都是用反射機制實現的。框架
IDE中當咱們構建出一個對象的時候,去調用該對象的方法和屬性的時候。一按點,編譯工具就會自動的把該對象可以使用的全部的方法和屬性所有都列出來,供用戶進行選擇。這就是利用了Java反射的原理,是對咱們建立對象的探知、自審。其餘例如Spring框架:IOC(控制反轉);Hibernate框架:關聯映射等;白盒測試等ide
4、反射機制的優勢與缺點函數
爲何要用反射機制?直接建立對象不就能夠了嗎,這就涉及到了動態與靜態的概念,工具
靜態編譯:在編譯時肯定類型,綁定對象,即經過。
動態編譯:運行時肯定類型,綁定對象。動態編譯最大限度發揮了java的靈活性,體現了多態的應用,有以下降類之間的藕合性。
優勢
(1)可以運行時動態獲取類的實例,大大提升系統的靈活性和擴展性。
(2)與Java動態編譯相結合,能夠實現無比強大的功能
缺點:
(1)使用反射的性能較低,使用反射基本上是一種解釋操做,咱們能夠告訴JVM,咱們但願作什麼而且它知足咱們的要求。這類操做老是慢於只直接執行相同的操做。
(2)使用反射相對來講不安全
(3)破壞了類的封裝性,能夠經過反射獲取這個類的私有方法和屬性
因此何時使用反射,就要靠業務的需求、大小,以及經驗的積累來決定。
5、反射機制的功能
首先Java 反射機制主要提供瞭如下功能
在運行時判斷任意一個對象所屬的類。 在運行時構造任意一個類的對象。 在運行時判斷任意一個類所具備的成員變量和方法。 在運行時調用任意一個對象的方法
反射的經常使用類和函數:Java反射機制的實現要藉助於4個類:Class,Constructor,Field,Method;
其中class表明的是類對象,Constructor-類的構造器對象,Field-類的屬性對象,Method-類的方法對象,
經過這四個對象咱們能夠粗略的看到一個類的各個組成部分。其中最核心的就是java.lang.Class這個類。它是Java反射機制的起源、基礎。當一個類被加載之後,Java虛擬機就會自動產生一個Class對象。經過這個Class對象咱們就能得到加載到虛擬機當中這個Class對象對應的方法、成員以及構造方法的聲明和定義等信息。應用反射時咱們最關心的通常是一個類的構造器、屬性和方法,下面咱們主要介紹Class類中針對這三個元素的方法:
一、獲得構造器的方法 Constructor getConstructor(Class[] params) -- 得到使用特殊的參數類型的公共構造函數, Constructor[] getConstructors() -- 得到類的全部公共構造函數 Constructor getDeclaredConstructor(Class[] params) -- 得到使用特定參數類型的構造函數(與接入級別無關) Constructor[] getDeclaredConstructors() -- 得到類的全部構造函數(與接入級別無關)
二、得到字段信息的方法
Field getField(String name) -- 得到命名的公共字段 Field[] getFields() -- 得到類的全部公共字段 Field getDeclaredField(String name) -- 得到類聲明的命名的字段 Field[] getDeclaredFields() -- 得到類聲明的全部字段
三、得到方法信息的方法
Method getMethod(String name, Class[] params) -- 使用特定的參數類型,得到命名的公共方法 Method[] getMethods() -- 得到類的全部公共方法 Method getDeclaredMethod(String name, Class[] params) -- 使用特寫的參數類型,得到類聲明的命名的方法 Method[] getDeclaredMethods() -- 得到類聲明的全部方法
在程序開發中使用反射並結合屬性文件,能夠達到程序代碼與配置文件相分離的目的,若是咱們想要獲得對象的信息,通常須要「引入須要的‘包.類’的名稱——經過new實例化——取得實例化對象」這樣的過程。使用反射就能夠變成「實例化對象——getClass()方法——獲得完整的‘包.類’名稱」這樣的過程。正常方法是經過一個類建立對象,反射方法就是經過一個對象找到一個類的信息。
6、如何運用反射機制
一、使用反射機制的步驟:
導入java.lang.relfect 包
遵循三個步驟
第一步是得到你想操做的類的 java.lang.Class 對象
第二步是調用諸如 getDeclaredMethods 的方法
第三步使用 反射API 來操做這些信息
二、得到Class對象的方法
1)若是一個類的實例已經獲得,你可使用
【Class c = 對象名.getClass(); 】
例: TextField t = new TextField();
Class c = t.getClass();
Class s = c.getSuperclass();
2)若是你在編譯期知道類的名字,你可使用以下的方法
Class c = java.awt.Button.class; 或者Class c = Integer.TYPE;
3)若是類名在編譯期不知道, 可是在運行期能夠得到, 你可使用下面的方法
Class c = Class.forName(strg);
這樣得到Class類對象的方法,實際上是利用反射API把指定字符串的類加載到內存中,因此也叫類加載器加載方法。此時類加載器會把該類的靜態方法和靜態屬性,以及靜態代碼所有加載到內存中。但此時類的對象尚未產生。因此這就是爲何靜態方法不能訪問非靜態屬性和方法。由於靜態方法和屬性產生的時機在非靜態屬性和方法以前。
7、直觀地認識反射
反射之中包含了一個「反」的概念,因此要想解釋反射就必須先從「正」開始解釋,通常而言,當用戶使用一個類的時候,應該先知道這個類,然後經過這個類產生實例化對象,可是「反」指的是經過對象找到類。
package cn.mldn.demo; class Person {} public class TestDemo { public static void main(String[] args) throws Exception { Person per = new Person() ; // 正着操做 System.out.println(per.getClass().getName()); // 反着來 } } |
以上的代碼使用了一個getClass()方法,然後就能夠獲得對象所在的「包.類」名稱,這就屬於「反」了,可是在這個「反」的操做之中有一個getClass()就做爲發起一切反射操做的開端。
Person的父類是Object類,而上面所使用getClass()方法就是Object類之中所定義的方法。取得Class對象:public final Class<?> getClass(),反射之中的全部泛型都定義爲?,返回值都是Object。而這個getClass()方法返回的對象是Class類的對象,因此這個Class就是全部反射操做的源頭。可是在講解其真正使用以前還有一個須要先解釋的問題,既然Class是全部反射操做的源頭,那麼這個類確定是最爲重要的,而若是要想取得這個類的實例化對象,Java中定義了三種方式:
方式一:經過Object類的getClass()方法取得,基本不用:
package cn.mldn.demo; class Person {} public class TestDemo { public static void main(String[] args) throws Exception { Person per = new Person() ; // 正着操做 Class<?> cls = per.getClass() ; // 取得Class對象 System.out.println(cls.getName()); // 反着來 } } |
方式二:使用「類.class」取得,在往後學習Hibernate開發的時候使用
package cn.mldn.demo; class Person {} public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Person.class ; // 取得Class對象 System.out.println(cls.getName()); // 反着來 } } |
方式三:使用Class類內部定義的一個static方法,主要使用
取得Class類對象:public static Class<?> forName(String className) throws ClassNotFoundException;
package cn.mldn.demo; class Person {} public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 System.out.println(cls.getName()); // 反着來 } } |
那麼如今一個新的問題又來了,取得了Class類的對象有什麼用處呢?對於對象的實例化操做以前一直依靠構造方法和關鍵字new完成,但是有了Class類對象以後,如今又提供了另一種對象的實例化方法:
經過反射實例化對象:public T newInstance() throws InstantiationException, IllegalAccessException;
範例:經過反射實例化對象
package cn.mldn.demo; class Person { @Override public String toString() { return "Person Class Instance ."; } } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 Object obj = cls.newInstance() ; // 實例化對象,和使用關鍵字new同樣 Person per = (Person) obj ; // 向下轉型 System.out.println(per); } } |
在進行Class.forName("cn.mldn.demo.Person")的時候,其實是對cn.mldn.demo.Person進行類加載,這時候,會把靜態屬性、方法以及靜態代碼塊都加載到內存中。但這時候,對象卻尚未產生。當執行cla.newInstance()的時候,就是利用反射機制將Class對象生成一個該類的一個實例。這時候對象就產生了。那麼如今能夠發現,對於對象的實例化操做,除了使用關鍵字new以外又多了一個反射機制操做,並且這個操做要比以前使用的new複雜一些,但是有什麼用?
對於程序的開發模式以前一直強調:儘可能減小耦合,而減小耦合的最好作法是使用接口,可是就算使用了接口也逃不出關鍵字new,因此實際上new是形成耦合的關鍵元兇。
範例:回顧一下以前所編寫的工廠設計模式
package cn.mldn.demo; interface Fruit { public void eat() ; } class Apple implements Fruit { public void eat() { System.out.println("吃蘋果。"); }; } class Factory { public static Fruit getInstance(String className) { if ("apple".equals(className)){ return new Apple() ; } return null ; } } public class FactoryDemo { public static void main(String[] args) { Fruit f = Factory.getInstance("apple") ; f.eat() ; } } |
以上爲以前所編寫最簡單的工廠設計模式,可是在這個工廠設計模式之中有一個最大的問題:若是如今接口的子類增長了,那麼工廠類確定須要修改,這是它所面臨的最大問題,而這個最大問題形成的關鍵性的病因是new,那麼若是說如今不使用關鍵字new了,變爲了反射機制呢?
反射機制實例化對象的時候實際上只須要「包.類」就能夠,因而根據此操做,修改工廠設計模式。
package cn.mldn.demo; interface Fruit { public void eat() ; } class Apple implements Fruit { public void eat() { System.out.println("吃蘋果。"); }; } class Orange implements Fruit { public void eat() { System.out.println("吃橘子。"); }; } class Factory { public static Fruit getInstance(String className) { Fruit f = null ; try { f = (Fruit) Class.forName(className).newInstance() ; } catch (Exception e) { e.printStackTrace(); } return f ; } } public class FactoryDemo { public static void main(String[] args) { Fruit f = Factory.getInstance("cn.mldn.demo.Orange") ; f.eat() ; } } |
發現,這個時候即便增長了接口的子類,工廠類照樣能夠完成對象的實例化操做,這個纔是真正的工廠類,能夠應對於全部的變化。若是單獨從開發角度而言,與開發者關係不大,可是對於往後學習的一些框架技術這個就是它實現的命脈,在往後的程序開發上,若是發現操做的過程之中須要傳遞了一個完整的「包.類」名稱的時候幾乎都是反射機制做用。
反射的深刻應用
以上只是利用了Class類做爲了反射實例化對象的基本應用,可是對於一個實例化對象而言,它須要調用類之中的構造方法、普通方法、屬性,而這些操做均可以經過反射機制完成。
調用構造
使用反射機制也能夠取得類之中的構造方法,這個方法在Class類之中已經明肯定義了:
No. | 方法名稱 | 類型 | 描述 |
1 | public Constructor<?>[] getConstructors() throws SecurityException | 普通 | 取得一個類的所有構造 |
2 | public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException | 普通 | 取得一個類的指定參數構造 |
如今發現以上的兩個方法返回的都是java.lang.reflect.Constructor類的對象。
範例:取得一個類之中的所有構造
package cn.mldn.demo; import java.lang.reflect.Constructor; class Person { // CTRL + K public Person() {} public Person(String name) {} public Person(String name,int age) {} } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 Constructor<?> cons [] = cls.getConstructors() ; // 取得所有構造 for (int x = 0; x < cons.length; x++) { System.out.println(cons[x]); } } } |
驗證:在以前強調的一個簡單Java類必須存在一個無參構造方法
範例:觀察沒有無參構造的狀況
package cn.mldn.demo; class Person { // CTRL + K private String name ; private int age ; public Person(String name,int age) { this.name = name ; this.age = age ; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 Object obj = cls.newInstance() ; // 實例化對象 System.out.println(obj); } } |
此時程序運行的時候出現了錯誤提示「java.lang.InstantiationException」,由於以上的方式使用反射實例化對象時須要的是類之中要提供無參構造方法,可是如今既然沒有了無參構造方法,那麼就必須明確的找到一個構造方法,然後利用Constructor類之中的新方法實例化對象:
實例化對象:public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException
package cn.mldn.demo; import java.lang.reflect.Constructor; class Person { // CTRL + K private String name ; private int age ; public Person(String name,int age) { this.name = name ; this.age = age ; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 // 取得指定參數類型的構造方法 Constructor<?> cons = cls.getConstructor(String.class,int.class) ; Object obj = cons.newInstance("張三", 20); // 爲構造方法傳遞參數 System.out.println(obj); } } |
很明顯,調用無參構造方法實例化對象要比調用有參構造的更加簡單、方便,因此在往後的全部開發之中,凡有簡單Java類出現的地方,都必定要提供無參構造。
調用普通方法
當取得了一個類實例化對象以後,下面最須要調用的確定是類之中的方法,因此能夠繼續使用Class類取得一個類中所定義的方法定義:
取得所有方法:public Method[] getMethods() throws SecurityException;
取得指定方法:public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
發現以上的方法返回的都是java.lang.reflect.Method類的對象。
範例:取得一個類之中所定義的所有方法
package cn.mldn.demo; import java.lang.reflect.Method; class Person { private String name ; public void setName(String name) { this.name = name; } public String getName() { return name; } } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 Method met [] = cls.getMethods() ; // 取得所有方法 for (int x = 0; x < met.length; x++) { System.out.println(met[x]); } } } |
可是取得了Method類對象最大的做用再也不於方法的列出(方法的列出都在開發工具上使用了),可是對於取得了Method類對象以後還有一個最大的功能,就是能夠利用反射調用類中的方法:
調用方法:public Object invoke(Object obj, Object... args) throws IllegalAccessException,IllegalArgumentException, InvocationTargetException
以前調用類中方法的時候使用的都是「對象.方法」,可是如今有了反射以後,能夠直接利用Object類調用指定子類的操做方法。(同時解釋一下,爲何setter、getter方法的命名要求如此嚴格)。
範例:利用反射調用Person類之中的setName()、getName()方法
package cn.mldn.demo; import java.lang.reflect.Method; class Person { private String name ; public void setName(String name) { this.name = name; } public String getName() { return name; } } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 Object obj = cls.newInstance(); // 實例化對象,沒有向Person轉型 String attribute = "name" ; // 要調用類之中的屬性 Method setMet = cls.getMethod("set" + initcap(attribute), String.class);// setName() Method getMet = cls.getMethod("get" + initcap(attribute));// getName() setMet.invoke(obj, "張三") ; // 等價於:Person對象.setName("張三") System.out.println(getMet.invoke(obj));// 等價於:Person對象.getName() } public static String initcap(String str) { return str.substring(0,1).toUpperCase().concat(str.substring(1)) ; } } |
在往後的全部框架技術開發之中,簡單Java類都是如此應用的,因此必須按照標準進行。
調用成員
類之中最後一個組成部分就是成員(Field,也能夠稱爲屬性),若是要經過反射取得類的成員可使用方法以下:
取得本類的所有成員:public Field[] getDeclaredFields() throws SecurityException;
取得指定的成員:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;
這兩個方法的返回值類型是java.lang.reflect.Field類的對象,下面首先觀察如何取得一個類之中的所有屬性。
範例:取得一個類之中的所有屬性
package cn.mldn.demo; import java.lang.reflect.Field; class Person { private String name ; } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class對象 Field field [] = cls.getDeclaredFields() ; // 取得所有屬性 for (int x = 0; x < field.length; x++) { System.out.println(field[x]); } } } |
可是找到Field實際上就找到了一個頗有意思的操做,在Field類之中提供了兩個方法:
設置屬性內容(相似於:對象.屬性 = 內容):
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;
取得屬性內容(相似於:對象.屬性):
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException
但是從類的開發要求而言,一直都強調類之中的屬性必須封裝,因此如今調用以前要想辦法解除封裝。
解除封裝:public void setAccessible(boolean flag) throws SecurityException;
範例:利用反射操做類中的屬性
package cn.mldn.demo; import java.lang.reflect.Field; class Person { private String name; } public class TestDemo { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象 Object obj = cls.newInstance(); // 對象實例化屬性纔會分配空間 Field nameField = cls.getDeclaredField("name") ; // 找到name屬性 nameField.setAccessible(true) ; // 解除封裝了 nameField.set(obj, "張三") ; // Person對象.name = "張三" System.out.println(nameField.get(obj)); // Person對象.name } } |
雖然反射機制運行直接操做類之中的屬性,但是不會有任何一種程序直接操做屬性,都會經過setter、getter方法。
以上就是對java反射機制的總結,內容大都來源於網友的總結,太多了就不一一指出,包括知乎和csdn等渠道,我只是對其加以合理性的組織了一下,方便本身和你們總結理解,若有不足和不對之處還望指正。