谈谈Java反射机制
寫在前面:
什么是java反射機(jī)制?我們又為什么要學(xué)它?
當(dāng)程序運(yùn)行時(shí),允許改變程序結(jié)構(gòu)或變量類型,這種語言稱為動(dòng)態(tài)語言。我們認(rèn)為java并不是動(dòng)態(tài)語言,但是它卻有一個(gè)非常突出的動(dòng)態(tài)相關(guān)機(jī)制,俗稱:反射。
IT行業(yè)里這么說,沒有反射也就沒有框架,現(xiàn)有的框架都是以反射為基礎(chǔ)。在實(shí)際項(xiàng)目開發(fā)中,用的最多的是框架,填的最多的是類,反射這一概念就是將框架和類揉在一起的調(diào)和劑。所以,反射才是接觸項(xiàng)目開發(fā)的敲門磚!
一、Class類
什么是Class類?
在面向?qū)ο蟮氖澜缋?#xff0c;萬事萬物皆是對(duì)象。而在java語言中,static修飾的東西不是對(duì)象,但是它屬于類。普通的數(shù)據(jù)類型不是對(duì)象,例如:int a = 5;它不是面向?qū)ο?#xff0c;但是它有其包裝類 Integer 或者分裝類來彌補(bǔ)了它。除了以上兩種不是面向?qū)ο?#xff0c;其余的包括類也有它的面向?qū)ο?#xff0c;類是java.lang.Class的實(shí)例化對(duì)象(注意Class是大寫)。也就是說:
Class A{}
當(dāng)我創(chuàng)建了A類,那么類A本身就是一個(gè)對(duì)象,誰的對(duì)象?java.lang.Class的實(shí)例對(duì)象。
那么這個(gè)對(duì)象又該怎么表示呢?
我們先看一下下面這段代碼:
| 1 2 3 4 | publicclass Demo(){ F f=newF(); } classF{} |
這里的F的實(shí)例化對(duì)象就可以用f表達(dá)出來。同理F類也是一個(gè)實(shí)例化對(duì)象,Class類的實(shí)例化對(duì)象。我們可以理解為任何一個(gè)類都是Class類的實(shí)例化對(duì)象,這種實(shí)例化對(duì)象有三種表示方法:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | publicclass Demo(){ F f=newF(); //第一種表達(dá)方式 Class c1=F.class;//這種表達(dá)方式同時(shí)也告訴了我們?nèi)魏我粋€(gè)類都有一個(gè)隱含的靜態(tài)成員變量class //第二種表達(dá)方式 Class c2=f.getClass();//這種表達(dá)方式在已知了該類的對(duì)象的情況下通過getClass方法獲取 //第三種表達(dá)方式 Class c3 = null; try{ c3 = Class.forName("com.text.F");//類的全稱 }catch(ClassNotFoundException e) { e.printStackTrace(); } } classF{} |
以上三種表達(dá)方式,c1,c2,c3都表示了F類的類類型,也就是官方解釋的Class Type。
那么問題來了:
| 1 | System.out.println(c1 == c2)?? or? System.out.println(c1 == c3)? |
答案是肯定的,返回值為ture。這表明不論c1 or c2 or c3都代表了F類的類類型,也就是說一個(gè)類只可能是Class類的一個(gè)實(shí)例對(duì)象。
理解了Class的概念,我們也可以通過類的類類型創(chuàng)建該類的對(duì)象實(shí)例,用c1 or c2 or c3的newInstance()方法:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | PublicclassDemo1{ try{ Foo foo = (Foo)c1.newInstance();//foo就表示F類的實(shí)例化對(duì)象 foo.print(); }catch(InstantiationException e) { e.printStackTrace(); }catch(IllegalAccessException e) { e.printStackTrace(); }} classF{ voidprint(){ } } |
這里需要注意的是,c1是F類的類類型,創(chuàng)建出來的就是F類的對(duì)象。如果a是A類的類類型,那么創(chuàng)建出來的對(duì)象也應(yīng)該與之對(duì)應(yīng),屬于A類的對(duì)象。
二、方法的反射
Class類有一個(gè)最簡(jiǎn)單的方法,getName():
| 1 2 3 4 5 6 7 8 9 10 11 | publicclass Demo2 { publicstatic void main(String[] args) { Class c1 = int.class;//int 的類類型 Class c2 = String.class;//String類的類類型 Class c3 = void.class; System.out.println(c1.getName()); System.out.println(c2.getName()); System.out.println(c2.getSimpleName()); System.out.println(c3.getName()); } } |
本的數(shù)據(jù)類型以及void關(guān)鍵字都是存在類類型的。
案例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | publicclass ClassUtil { publicstatic void printClassMethodMessage(Object obj){ //要獲取類的信息》》首先我們要獲取類的類類型 Class c = obj.getClass(); //我們知道Object類是一切類的父類,所以我們傳遞的是哪個(gè)子類的對(duì)象,c就是該子類的類類型。 //接下來我們要獲取類的名稱 System.out.println("類的名稱是:"+c.getName()); /* *我們知道,萬事萬物都是對(duì)象,方法也是對(duì)象,是誰的對(duì)象呢? * 在java里面,方法是Method類的對(duì)象 *一個(gè)成員方法就是一個(gè)Method的對(duì)象,那么Method就封裝了對(duì)這個(gè)成員 *方法的操作 */ //如果我們要獲得所有的方法,可以用getMethods()方法,這個(gè)方法獲取的是所有的Public的函數(shù),包括父類繼承而來的。如果我們要獲取所有該類自己聲明的方法,就可以用getDeclaredMethods()方法,這個(gè)方法是不問訪問權(quán)限的。 Method[] ms = c.getMethods();//c.getDeclaredMethods() //接下來我們拿到這些方法之后干什么?我們就可以獲取這些方法的信息,比如方法的名字。 //首先我們要循環(huán)遍歷這些方法 for(inti = 0; i < ms.length;i++){ //然后可以得到方法的返回值類型的類類型 Class returnType = ms[i].getReturnType(); //得到方法的返回值類型的名字 System.out.print(returnType.getName()+" "); //得到方法的名稱 System.out.print(ms[i].getName()+"("); //獲取參數(shù)類型--->得到的是參數(shù)列表的類型的類類型 Class[] paramTypes = ms[i].getParameterTypes(); for(Class class1 : paramTypes) { System.out.print(class1.getName()+","); } System.out.println(")"); } } } |
總結(jié)思路:
通過方法的反射得到該類的名稱步驟:
1.獲取該類的類類型
2.通過類類型獲取類的方法(getMethods())
3.循環(huán)遍歷所獲取到的方法
4.通過這些方法的getReturnType()得到返回值類型的類類型,又通過該類類型得到返回值類型的名字
5.getName()得到方法的名稱,getParameterTypes()獲取這個(gè)方法里面的參數(shù)類型的類類型。
三、成員變量的反射
首先我們需要認(rèn)識(shí)到成員變量也是對(duì)象,是java.lang.reflect.Field類的對(duì)象,那么也就是說Field類封裝了關(guān)于成員變量的操作。既然它封裝了成員變量,我們又該如何獲取這些成員變量呢?它有這么一個(gè)方法:
| 1 2 3 4 5 | publicclass ClassUtil { publicstatic void printFieldMessage(Object obj){ Class c = obj.getClass(); //Field[] fs = c.getFields(); } |
這里的getFields()方法獲取的所有的public的成員變量的信息。和方法的反射那里public的成員變量,也有一個(gè)獲取所有自己聲明的成員變量的信息:
Field[] fs = c.getDeclaredFields();
我們得到它之后,可以進(jìn)行遍歷(既然封裝了Field的信息,那么我們就可以得到Field類型)
| 1 2 3 4 5 6 7 8 | for(Field field : fs) { //得到成員變量的類型的類類型 Class fieldType = field.getType(); String typeName = fieldType.getName(); //得到成員變量的名稱 String fieldName = field.getName(); System.out.println(typeName+" "+fieldName); } |
四、構(gòu)造函數(shù)的反射
不論是方法的反射、成員變量的反射、構(gòu)造函數(shù)的反射,我們只需要知道:要想獲取類的信息,首先得獲取類的類類型。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | publicstatic void printConMessage(Object obj){ Class c = obj.getClass(); /* * 首先構(gòu)造函數(shù)也是對(duì)象,是java.lang.Constructor類的對(duì)象 * 也就是java.lang. Constructor中封裝了構(gòu)造函數(shù)的信息 * 和前面說到的一樣,它也有兩個(gè)方法: * getConstructors()方法獲取所有的public的構(gòu)造函數(shù) * getDeclaredConstructors()方法得到所有的自己聲明的構(gòu)造函數(shù) */ //Constructor[] cs = c.getConstructors(); Constructor[] cs = c.getDeclaredConstructors(); for(Constructor constructor : cs) { //我們知道構(gòu)造方法是沒有返回值類型的,但是我們可以: System.out.print(constructor.getName()+"("); //獲取構(gòu)造函數(shù)的參數(shù)列表》》得到的是參數(shù)列表的類類型 Class[] paramTypes = constructor.getParameterTypes(); for(Class class1 : paramTypes) { System.out.print(class1.getName()+","); } System.out.println(")"); } } |
五、Class類的動(dòng)態(tài)加載類
如何動(dòng)態(tài)加載一個(gè)類呢?
首先我們需要區(qū)分什么是動(dòng)態(tài)加載?什么是靜態(tài)加載?我們普遍認(rèn)為編譯時(shí)刻加載的類是靜態(tài)加載類,運(yùn)行時(shí)刻加載的類是動(dòng)態(tài)加載類。我們舉一個(gè)例子:
| 1 2 3 4 5 6 7 8 9 10 11 12 | Class A{ Publicstaticvoid main(String[] args){ if("B".equal(args[0])){ B b=newB(); b.start(); } if("C".equal(args[0])){ C c=newC(); C.start(); } } } |
上面這一段代碼,當(dāng)我們?cè)谟胑clipse或者myeclipse的時(shí)候我們并不關(guān)心是否能夠通過編譯,當(dāng)我們直接在cmd使用javac訪問A.java類的時(shí)候,就會(huì)拋出問題:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | A.java:7:錯(cuò)誤:找不到符號(hào) B b=newB(); 符號(hào):? 類B 位置: 類A A.java:7:錯(cuò)誤:找不到符號(hào) B b=newB(); 符號(hào):? 類B 位置: 類A A.java:12:錯(cuò)誤:找不到符號(hào) C c=newC(); 符號(hào):? 類C 位置: 類A A.java:12:錯(cuò)誤:找不到符號(hào) C c=newC(); 符號(hào):? 類C 位置: 類A 4個(gè)錯(cuò)誤 |
或許我們理所當(dāng)然的認(rèn)為這樣應(yīng)該是錯(cuò),類B根本就不存在。但是如果我們多思考一下,就會(huì)發(fā)現(xiàn)B一定用嗎?不一定。C一定用嗎?也不一定。那么好,現(xiàn)在我們就讓B類存在
| 1 2 3 4 5 | Class B{ Publicstaticvoid start(){ System.out.print("B...satrt"); } } |
現(xiàn)在我們就先 javac B.class,讓B類先開始編譯。然后在運(yùn)行javac A.class。結(jié)果是:
| 1 2 3 4 5 6 7 8 9 | A.java:12:錯(cuò)誤:找不到符號(hào) C c=newC(); 符號(hào):? 類C 位置: 類A A.java:12:錯(cuò)誤:找不到符號(hào) C c=newC(); 符號(hào):? 類C 位置: 類A 2個(gè)錯(cuò)誤 |
我們?cè)傧?#xff0c;這個(gè)程序有什么問題。如果你說沒有什么問題?C類本來就不存在啊!那么問題來了B類已經(jīng)存在了,假設(shè)我現(xiàn)在就想用B,我們這個(gè)程序用得了嗎?答案是肯定的,用不了。那用不了的原因是什么?因?yàn)槲覀冞@個(gè)程序是做的類的靜態(tài)加載,也就是說new創(chuàng)建對(duì)象是靜態(tài)加載類,在編譯時(shí)刻就需要加載所有的,可能使用到的類。所以不管你用不用這個(gè)類。
現(xiàn)在B類是存在的,但是我們這個(gè)程序仍然用不了,因?yàn)闀?huì)一直報(bào)C類有問題,所以B類我也用不了。那么在實(shí)際應(yīng)用當(dāng)中,我們肯定需要如果B類存在,B類我就能用,當(dāng)用C類的時(shí)候,你再告訴我錯(cuò)了。如果說將來你有100個(gè)類,只要其中一個(gè)類出現(xiàn)問題,其它99個(gè)類你都用不了。所以這并不是我們想要的。
我們想要的就是我用那個(gè)類就加載那個(gè)類,也就是常說的運(yùn)行時(shí)刻加載,動(dòng)態(tài)加載類。如何實(shí)現(xiàn)動(dòng)態(tài)加載類呢?我們可以建這么一個(gè)類:
| 1 2 3 4 5 6 7 8 9 10 11 | Class All{ Publicstaticvoid start(){ try{ Class cl= Class.forName(args[0]); //通過類類型,創(chuàng)建該類的對(duì)象 cl.newInstance(); }catch(Exception e){ e.printStackTrace(); } } } |
前面我們?cè)诜治鯟lass實(shí)例化對(duì)象的方式的時(shí)候,Class.forName(“類的全稱”),它不僅僅表示了類的類類型,還表示了動(dòng)態(tài)加載類。當(dāng)我們javac All.java的時(shí)候,它不會(huì)報(bào)任何錯(cuò)誤,也就是說在編譯的時(shí)候是沒有錯(cuò)誤的。只有當(dāng)我們具體用某個(gè)類的時(shí)候,那個(gè)類不存在,它才會(huì)報(bào)錯(cuò)。
如果加載的類是B類,就需要:
| 1 | B bt = (B) cl.newInstance(); |
萬一加載的是C類呢,可以改成
| 1 | C ct = (C) cl.newInstance(); |
但是如果我想用很多的類或者加載很多的類,該怎么辦?我們可以統(tǒng)一一個(gè)標(biāo)準(zhǔn),不論C類還是B類或者其他的類,比如定義一個(gè)標(biāo)準(zhǔn)
| 1 | Stand s = (Stand) cl.newInstance(); |
只要B類和C類都是這個(gè)標(biāo)準(zhǔn)的就行了。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Class All{ Publicstaticvoid start(){ try{ Class cl= Class.forName(args[0]); //通過類類型,創(chuàng)建該類的對(duì)象 Stand s = (Stand) cl.newInstance(); s.start(); }catch(Exception e){ e.printStackTrace(); } } } interfaceStand { Publicvoidstart(); } |
現(xiàn)在如果我想要用B類,我們只需要:
| 1 2 3 4 5 | Class B implementsStand{ Publicvoidstart(){ System.out.print("B...satrt"); } } |
加載B類,編譯運(yùn)行。
| 1 2 3 | javac B.java javac Stand.java java Stand B |
結(jié)果:
| 1 | B...satrt |
如果以后想用某一個(gè)類,不需要重新編譯,只需要實(shí)現(xiàn)這個(gè)標(biāo)準(zhǔn)的接口即可。只需要?jiǎng)討B(tài)的加載新的東西就行了。
這就是動(dòng)態(tài)加載類。
from:?http://www.importnew.com/23560.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的谈谈Java反射机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java并发:volatile内存可见性
- 下一篇: 提升编程水平的靠谱方法