前幾天被問到了反射,當時沒有回答出來多少,後來去看了一下,這裏大概總結一下!java
首先,咱們要知道反射機制,那麼什麼是反射呢?程序員
答:反射是程序能夠訪問、檢測和修改他自己狀態或行爲的一種能力。那麼java語言是如何支持反射的呢?別急,咱們來慢慢聊。數組
咱們之前的學習中有遇到過Java中萬事萬物皆爲對象之說,那麼靜態變量呢?還有基本數據類型的數據呢?它們也是面向對象的嗎?咱們都知道靜態是屬於類的,不是哪一個類的對象的,基本數據類型是屬於包裝類的,那麼難道類也是對象?答案就是是。Java中的每個類都是java.lang.Class類的對象。框架
1、Class類的使用jvm
在Java中,每個class都有一個相應的Class對象。換句話說,就是當咱們編寫一個類時,編譯完成後,在生成的.class文件中,就會產生一個Class對象,用於表示這個類的類型信息。也就是說,Class類的實例對象表示java應用程序運行時的類或者接口。虛擬機爲每種類型管理一個獨一無二的Class對象,也就是說,每一個類型都有一個Class對象,運行程序時,jvm首先檢查所要加載的類對應的Class對象是否已經加載,若是沒有加載,jvm就會根據類名查找.class文件並將其Class對象載入。學習
能夠經過類名.class、類的對象.getClass()、Class.forName()三種方法獲取Class對象。具體的使用以下:測試
package Reflect; public class ClassRefelect { public static void main(String[] args){ //得到Foo的類對象 Foo foo1 = new Foo(); /** * 得到Foo類對象有三種方法 * 1.類名.class 每個類都有一個隱含的成員變量,class * 2.類對象.getClass(); * 3.Class.forName(包名+類名); * 下面的c1,c2,c3官網稱之爲「Class Type" 類類型 */ /** * 萬事萬物皆爲對象,那麼類也是對象,類是什麼的對象呢》 *java.lang.Class的對象 * 該類中封裝了類的相關操做 */ Class c1 = Foo.class; Class c2 = foo1.getClass(); Class c3 = null; try { c3 = Class.forName("Reflect.Foo"); } catch (ClassNotFoundException e) { e.printStackTrace(); } /** *那麼這三個類類型是否相同呢? * 答案是相同,爲何呢? * 由於每個類只可能有一種類類型, * 就好比每個對象只有一個類同樣, * 這個類類型是Class的實例對象 */ System.out.println(c1==c2); //true System.out.println(c3==c2); //true } } class Foo{ public void print(){ System.out.println("建立了Foo類的對象"); } }
能夠看到上面的代碼中有對三個Class對象的引用變量進行比較,答案固然是true,由於它們都是Foo類對象,而上面咱們也提到了,每個類都有一個獨一無二的Class對象,因此結果應該是true.this
這裏還有一個問題,咱們怎麼區分foo1和c一、c2等呢?咱們知道foo1是類Foo的一個對象,那麼就是Foo的對象,官網上對於c1,c2有一種說法,是Class Type----->類類型,也就是c1,c2是Foo的類類型,其實咱們還能夠這樣區別,c1,c2是Foo對象,而foo1是Foo的對象。spa
既然咱們已經獲得了Foo的類類型,裏面含有Foo類的相關信息,那麼咱們有一個大膽的想象,可否用Foo類類型獲得Foo的某個對象呢?答案是固然能夠。Class對象有一個newInstance()方法,代碼演示以下:3d
package Reflect; public class ClassRefelect { public static void main(String[] args){ Class c1 = Foo.class; try { Foo foo2 = (Foo) c1.newInstance(); foo2.print(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } class Foo{ public void print(){ System.out.println("建立了Foo類的對象"); } }
這裏的foo2至關於new Foo();建立出來的對象。
下面咱們來聊聊方法的反射。
2、方法的反射
要想了解方法的反射,咱們首先要得到Class對象,才能經過Class對象得到方法的相關信息,得到Class對象的方法上面已經講過,這裏插播一條廣告,上面不是提到了每種類型都有Class對象麼,那麼基本數據類型以及void類型有木有呢?恩,有的,看下面的代碼
package Reflect; /** * 基本數據類型類類型 * 包裝類類類型 * */ public class ClassType { public static void main(String[] args){ Class c1 = int.class; //int的類類型 Class c2 = String.class; //String類類型 Class c3 = Double.class; //Double包裝類類型 Class c4 = double.class; //double類類型 Class c5 = void.class; //void 的類類型 System.out.println(c1.getName()); //int System.out.println(c2.getName()); //java.lang.String System.out.println(c2.getSimpleName()); //String System.out.println(c3.getName()); //java.lang.Double System.out.println(c4.getName()); //double System.out.println(c3.getSimpleName()); //Double System.out.println(c5.getName()); //void } }
既然能夠拿到Class對象,那麼獲取方法的一系列信息就不成問題了。
方法也是對象,是Method類的對象,該類中封裝了方法的一系列操做,該類是java.lang.reflect包下的類。
這裏介紹幾個方法,後面會用到
----------------------------------------------------------------------------------------------------------------------------------------------------------------
1.getMethods() //得到一個類中的全部public修飾的方法對象,包括繼承父類的方法
2.getDeclaredMethods() //得到一個類中的全部本身聲明的方法對象,不問訪問權限,不包括父類的
3.getName() //得到調用者的名稱
4.getReturnType() //得到方法的返回值類類型,即若是返回值是int,那麼返回的是int.class
5.getParameterTypes() //得到參數列表的類類型
----------------------------------------------------------------------------------------------------------------------------------------------------------------
package Reflect; import java.lang.reflect.Method; public class ClassUtil { public static void printMessage(Object obj){ //得到類的類類型 /** * 若是傳入的參數是Object類型, * 則或取的就是Object的類類型, * 若是是其子類,則獲取的就是其子類的類類型 */ Class c1 = obj.getClass(); //或取類的名稱 System.out.println("類的名稱是:"+c1.getName()); //獲取類中的方法 //獲取類中的方法 Method[] ms = c1.getMethods(); for(int i=0;i<ms.length;i++){ //獲取方法的返回值類型 Class returnType = ms[i].getReturnType(); System.out.print(returnType.getName()+" "); //獲取方法的名稱 System.out.print(ms[i].getName()+"("); //獲取參數類型----》獲取的是參數列表的類類型 Class[] parameterType = ms[i].getParameterTypes(); for (Class class1:parameterType) { System.out.print(class1.getName()+","); } System.out.println(")"); } }
這裏傳入的若是是Object類型的對象,那麼就會獲取Object類的方法信息,若是是Object類的子類,那麼獲取的就是
該子類的方法信息。
這裏咱們測試一下:
package Reflect; public class TestClassUtil { public static void main(String[] args){ Integer s = 1; ClassUtil.printMessage(s); } }
從這裏咱們能夠看到打印出了Integer類的全部的public方法的信息。
那麼什麼是方法的反射呢?方法的反射是什麼樣的呢?
平時咱們使用方法的步驟是什麼呢?是否是先獲取一個類的對象(若是是實例方法),而後對象.方法()對嗎?那麼方法反射剛好相反,是利用方法對象操做類的對象的。
方法的反射也有幾個步驟:
0.獲取類類型
1.經過類類型獲取某個方法的方法對象
2.方法對象.invoke(類的對象,參數列表)
invoke()方法API文檔上時這樣講的:「對帶有指定參數的指定對象調用由此 Method
對象表示的底層方法」,通俗的說就是能夠利用invoke()方法進行反射。
下面列出反射中須要的方法:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
****說明一下:如下提到的c是Class對象,某個類的類類型,method是1,2方法返回的方法對象****
1.getMethod("Method name","Parameter Type") //方法名稱和參數列表能夠惟一肯定一個方法,注意這個方法 只能獲取類中public 聲明的方法,包括繼承自父類的方法
用法舉例:public void print(int a,int b){}
c.getMethod(「print",int.class,int.class) 或者 c.getMethod("print",new Class[]{int.class,int.class})
2.getDeclaredMethod("Method name","Parameter Type") //獲取類中本身聲明的方法,不問權限,不包括繼承自父類的方法
用法舉例:public void print(int a,int b){}
c.getDeclaredMethod(「print",int.class,int.class) 或者 c.getDeclaredMethod("print",new Class[]{int.class,int.class})
以上兩個方法都是獲取方法對象的
3.invoke(Object obj,args) //對帶有args參數的obj對象調用由此Method對象表示的底層方法
用法舉例:class A{
public void print(int a,int b){}
}
method.invoke(new A(),10,20) 或者 method.invoke(new A(), new Object[]{10,20});
invoke方法返回值是null,或者是Object或者Object的子類,當是Object時不存在問題,若是(Object的子類)想要具體的返回值類型,那麼必須進行強制類型轉換,如
Object o = method.invoke(a1,10,10);
Integer i = (Integer)method.invoke(a1,10,10);
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下來咱們看具體的代碼:
package Reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MethodRefelect { public static void main(String[] args){ /** * 1.要想獲取一個方法的信息,首先須要獲取類類型 * 2.獲取了類類型後經過類類型獲得方法對象 * 3.經過方法對象能夠反射操做方法 */ A a = new A(); Class c = a.getClass(); try { Method m = c.getMethod("print",new Class[]{int.class,int.class}); try { // Object o = m.invoke(a,new Object[]{10,20}); Integer o = (Integer)m.invoke(a,10,20); System.out.println(o); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } try { Method m2 = c.getDeclaredMethod("print",String.class,String.class); try { Object o = m2.invoke(a,new Object[]{"Hello","ninhao"}); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } try { // Method m3 = c.getDeclaredMethod("print",new Class[]{}); Method m3 = c.getDeclaredMethod("print"); try { // Object o = m3.invoke(a,new Object[]{}); Object o = m3.invoke(a); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } } class A{ public void print(){ System.out.println("hello"); } public int print(int a,int b){ System.out.println(a+b); return a+b; } public void print(String a,String b){ System.out.println(a.toUpperCase()+","+b.toLowerCase()); } }
方法的反射講完後,咱們說一下Field的反射操做
3、Field的反射
在講Field的反射以前,先來了解一下如何經過Class對象獲取某個類的Field的信息,若是須要獲取Field的信息,首先須要得到類中全部的Filed,而後依次得到每個Field的數據類型和名稱。
接下來列出得到Field的信息的方法
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.getDeclaredFields() //得到一個類中所聲明的全部的Filed,返回值是一個數組
2.getType() //得到調用者的類型
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
代碼相對來講簡單,以下:
public static void printFiled(Object obj) { Class c1 = obj.getClass();
Field[] fs = c1.getDeclaredFields(); //本身類中聲明的全部的成員變量 for(Field filed: fs){ //根據每個成員變量獲取成員變量的類型 Class returnType = filed.getType(); String returnName = returnType.getName(); //返回每個成員變量的名稱 String FiledName = filed.getName(); System.out.println(returnName+" "+FiledName); } }
package Reflect; public class TestClassUtil { public static void main(String[] args){ Integer s = 1; ClassUtil.printFiled(s); } }
測試了一下,結果中列出了Integer類的Field.[C這個是數組類型的反射,這裏不作詳述。
接着到了咱們Field的反射表演的時間了。
Field的反射操做步驟以下:
0.得到類類型
1.經過類類型得到某個Field
1.5.通常,Field是private,因此須要setAccessible(true),大概的意思是能夠操做private的數據
2.對得到的Field進行相關操做
補充一下:Field(成員變量)也是面向對象的,它是java.lang.reflect.Filed的實例對象
具體的實踐以下:
package Reflect; import java.lang.reflect.Field; public class FieldReflect { public static void main(String[] args){ B b = new B(); Class c = b.getClass(); //首先獲取成員變量對象 try { /** * 這裏的getField之因此會出錯, * 是由於getField獲取的是public的, * 而以前我並無指定a的訪問權限 */ // Field f = c.getField("a"); Field f = c.getDeclaredField("a"); //經過getDeclaredField("Field name");得到該屬性對象 try { f.setAccessible(true); //若是不設置該標誌,將不能訪問私有成員 f.set(b,12); //平時咱們都是b.setF(xxx);這裏經過Field對象f反向操做B類的對象b System.out.println(f.get(b)); //12 } catch (IllegalAccessException e) { e.printStackTrace(); } } catch (NoSuchFieldException e) { e.printStackTrace(); } } } class B{ private int a; private int b; public void setA(int a) { this.a = a; } public void setB(int b) { this.b = b; } public int getA() { return a; } public int getB() { return b; } }
4、構造方法的反射
首先咱們仍是來獲取構造方法的信息,構造方法的獲取所須要的方法同上面的普通方法、成員變量大概類似,這裏不作贅述。直接看代碼便可理解:
public static void printConMessage(Object obj){ //得到類類型 Class c1 = obj.getClass(); //得到類中的本身聲明的構造方法 Constructor[] constructors = c1.getDeclaredConstructors(); for(Constructor constructor:constructors){ //得到構造方法的名稱 System.out.print(constructor.getName()+"("); //得到構造方法的參數列表中的參數類型 Class[] parameterTypes = constructor.getParameterTypes(); //打印出每個構造方法的參數 for (Class para: parameterTypes) { System.out.print(para.getName()+","); } System.out.println(")"); } }
package Reflect; public class TestClassUtil { public static void main(String[] args){ Integer s = 1; ClassUtil.printConMessage(s); } }
構造方法也是對象,是java.lang.reflect.Constructor的實例對象
反射所須要的步驟:
1.根據類類型得到構造方法對象
2.經過構造方法對象建立該類的對象
下面的是構造方法的反射的代碼:
package Reflect; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class ConRefelect { public static void main(String[] args){ //構造方法的反射操做 C c = new C(); Class c1 = c.getClass(); //得到構造方法 try { Constructor constructor1 = c1.getConstructor(); Constructor constructor2 = c1.getConstructor(String.class); try { /** * 類類型.newInstance()能夠建立一個類對象 * 類的構造對象.newInstance()也能夠建立一個類對象 * 這個的意思是 */ C o = (C)constructor1.newInstance(); C q = (C)constructor2.newInstance("hello"); q.print(); o.print(); // System.out.println(c1==constructor1); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } System.out.println(constructor1.getName()); System.out.println(constructor2.getName()); } catch (NoSuchMethodException e) { e.printStackTrace(); } } } class C{ public String a; public C(){ } public C(String a){ this.a = a; } public void print(){ System.out.println("構造方法被反射了"); } }
經過反射咱們還能夠查看泛型的本質原理:
5、經過反射了解泛型的本質
泛型指的是集合中的數據只能輸入指定的數據類型的數據。以下所示:
ArrayList<String> list = new ArrayList<String>();
這個list中只能輸入String 類型的數據,若是輸入其餘的不兼容的就會報錯。
那麼如今咱們經過反射了解反射:
package Reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; public class TestGeneric { public static void main(String[] args){ ArrayList list = new ArrayList(); ArrayList<String> list1 = new ArrayList<String>(); list1.add("hello"); // list1.add(20); //下面經過反射來判斷泛型的本質 Class c = list1.getClass(); try { Method m = c.getMethod("add",new Class[]{Object.class}); try { Object o = m.invoke(list1,20); System.out.println(list1.size()); //2 System.out.println(list1); [hello,20] } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } }
上面的代碼中剛開始時在list1中插入20時編譯報錯,接着咱們經過反射得到方法對象,操做集合的add方法,將20成功插入了集合中。爲何呢?由於反射是運行時進行的,因此上面咱們繞過了編譯將20成功插入了集中由此可得出泛型其實是在編譯時設置了「路障」,在編譯後會驅泛型化,它的存在只是爲了放置錯誤的輸入,只在編譯時有效,繞過編譯則無效。
反射大概就總結這些,接下來咱們小小的聊聊動態加載類。
6、動態加載類
類的加載分爲靜態加載和動態加載,靜態加載指的是編譯時加載,動態加載指的是運行時加載。
new 建立對象時是靜態加載類,在編譯時刻就須要將所須要的全部的類加載進來。這樣有一個問題就是,當咱們有一個功能類,個人功能還不是很完整,可是我須要提早搭建起來框架,可是編譯時發現有一個如今不使用的類不存在,因此我如今這個功能類則編譯不經過。具體的代碼演示以下:
public class test{ public static void main(String[] args){ Word word = new Word(); word.start(); Excel excel = new Excel(); excel.start(); } class Word{ void start(){}; }
如上所示,當我有一個Word類時,我就想要編譯運行這個test類,測試一下個人Word類是不是好的,可是如今沒有Excel類,因此沒法檢測,這就是高耦合,依賴性太強,這在工程中是不會出現的,咱們必須使得咱們的代碼低耦合,高內聚才能符合軟件工程的要求。
那麼如何改善呢?這個本質的緣由仍是由於類是靜態加載,若是改成動態加載,那麼就不會出現這個問題,我只須要在動態加載時發現導入我須要的類便可。
package Reflect; public class TestLoose { public static void main(String[] args){ try { Class a = Class.forName(args[0]); //動態加載類,在編譯時根本不會出現問題,只有 try { Able able = (Able)a.newInstance(); able.start(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } interface Able{ public void start(); } class Word implements Able{ public void start(){} }
上面的代碼就是一個低耦合,高內聚的代碼規範,如何說呢。咱們能夠從編譯、運行兩部分來講明,首先在編譯時TestLoose是不會出錯的,由於咱們有一個Able的接口存在,因此編譯經過;當運行時咱們有Word類,因此咱們在運行時輸入Word也是不會報錯的,咱們輸入Excel纔會出錯,這樣就會解決上面的若是存在一個類而不能測的問題,若是咱們後邊須要或者這段代碼由別的程序員來繼續寫,它們只須要實現Able便可,如Excel implements Able;便可繼續添加。
這是編寫代碼的一種新思想,從高強大的依賴種脫身而出。
總結:今天總結關於反射的問題,就到這裏了。這裏沒有提到反射的數組和動態代理等關於反射更多、更復雜的東西,只是基礎的知識。