Java基礎(chǔ)篇之反射機(jī)制詳解
思考:在講反射之前,先思考一個(gè)問題,java中如何創(chuàng)建一個(gè)對象,有哪幾種方式?
Java中創(chuàng)建對象大概有這幾種方式:
- 1、使用new關(guān)鍵字:這是我們最常見的也是最簡單的創(chuàng)建對象的方式
- 2、使用Clone的方法:無論何時(shí)我們調(diào)用一個(gè)對象的clone方法,JVM就會創(chuàng)建一個(gè)新的對象,將前面的對象的內(nèi)容全部拷貝進(jìn)去
- 3、使用反序列化:當(dāng)我們序列化和反序列化一個(gè)對象,JVM會給我們創(chuàng)建一個(gè)單獨(dú)的對象
上邊是Java中常見的創(chuàng)建對象的三種方式,其實(shí)除了上邊的三種還有另外一種方式,就是接下來我們要討論的 “反射”
1、反射概述
1.1什么是反射
反射就是把Java類中的各個(gè)部分,映射成一個(gè)個(gè)的Java對象,拿到這些對象后可以做一些事情。
既然說反射是反射Java類中的各個(gè)組成部分,所以說咱們得知道一個(gè)類中有哪兒些部分?
例如,一個(gè)類有:成員變量,方法,構(gòu)造方法,等信息,利用反射技術(shù)咱們可以把這些組成部分映射成一個(gè)個(gè)對象。
1.2、反射能干什么
說完反射的概念后,咱們說一下反射能干什么?
一般來說反射是用來做框架的,或者說可以做一些抽象度比較高的底層代碼,反射在日常的開發(fā)中用到的不多,但是咱們還必須搞懂它,因?yàn)楦愣朔瓷湟院螅梢詭椭蹅兝斫饪蚣艿囊恍┰?。所以說有一句很經(jīng)典的話:反射是框架設(shè)計(jì)的靈魂。現(xiàn)在說完這個(gè)可能還不太能理解,不急,等下說完一個(gè)快速入門的例子后,應(yīng)該會稍微有點(diǎn)感覺
1.3、怎么得到想反射的類
剛才已經(jīng)說過,反射是對一個(gè)類進(jìn)行解剖,想解剖一個(gè)東西,前提是首先你得拿到這個(gè)東西,那么怎么得到咱們想解剖的類呢?
首先大家要明白一點(diǎn),咱們寫的代碼是存儲在后綴名是 .java的文件里的,但是它會被編譯,最終真正去執(zhí)行的是編譯后的 .class文件。Java是面向?qū)ο蟮恼Z言,一切皆對象,所以java認(rèn)為 這些編譯后的 class文件,這種事物也是一種對象,它也給抽象成了一種類,這個(gè)類就是Class,大家可以去AIP里看一下這個(gè)類
所以拿到這個(gè)類后,就相當(dāng)于拿到了咱們想解剖的類,那怎么拿到這個(gè)類?
看API文檔后,有一個(gè)方法forName(String className); 而且是一個(gè)靜態(tài)的方法,這樣咱們就可以得到想反射的類了
到這里,看Class clazz = Class.forName("com.cj.test.Person");
這個(gè)應(yīng)該有點(diǎn)感覺了吧
Class.forName("com.cj.test.Person");
因?yàn)檫@個(gè)方法里接收的是個(gè)字符串,字符串的話,我們就可以寫在配置文件里,然后利用反射生成我們需要的對象,這才是我們想要的。很多框架里都有類似的配置
2、解剖類
我們知道一個(gè)類里一般有構(gòu)造函數(shù)、方法、成員變量(字段/屬性)這三部分組成
翻閱API文檔,可以看到
Class對象提供了如下常用方法:
- public Constructor getConstructor(Class<?>…parameterTypes)
- public Method getMethod(String name,Class<?>… parameterTypes)
- public Field getField(String name)
- public Constructor getDeclaredConstructor(Class<?>…parameterTypes)
- public Method getDeclaredMethod(String name,Class<?>… parameterTypes)
- public Field getDeclaredField(String name)
這些方法分別用于幫咱們從類中解剖出構(gòu)造函數(shù)、方法和成員變量(屬性)。
然后把解剖出來的部分,分別用Constructor、Method、Field對象表示。
2.1反射構(gòu)造方法
2.1.1反射無參的構(gòu)造函數(shù)
可以看到 默認(rèn)的無參構(gòu)造方法執(zhí)行了
從上邊的例子看出,要想反射,首先第一步就是得到類的字節(jié)碼
所以簡單說一下得到類的字節(jié)碼的幾種方式
- (1)、Class.forName("com.cj.test.Person"); 這就是上邊我們用的方式
- (2)、對象.getClass();
- (3)、類名.class;
2.1.2反射“一個(gè)參數(shù)”的構(gòu)造函數(shù)
2.1.3反射“多個(gè)參數(shù)”的構(gòu)造函數(shù)
2.1.4反射“私有”的構(gòu)造函數(shù)
注意:在反射私有的構(gòu)造函數(shù)時(shí),用普通的clazz.getConstructor()會報(bào)錯(cuò),因?yàn)樗撬接械模蕴峁┝藢iT反射私有構(gòu)造函數(shù)的方法 clazz.getDeclaredConstructor(int.class);//讀取私有的構(gòu)造函數(shù)
,用這個(gè)方法讀取完還需要設(shè)置一下暴力反射才可以
c.setAccessible(true);//暴力反射
2.1.5反射得到類中所有的構(gòu)造函數(shù)
2.2反射類中的方法
package com.cj.test; import java.util.Date; public class Person { public Person(){ System.out.println("默認(rèn)的無參構(gòu)造方法執(zhí)行了"); } public Person(String name){ System.out.println("姓名:"+name); } public Person(String name,int age){ System.out.println(name+"="+age); } private Person(int age){ System.out.println("年齡:"+age); } public void m1() { System.out.println("m1"); } public void m2(String name) { System.out.println(name); } public String m3(String name,int age) { System.out.println(name+":"+age); return "aaa"; } private void m4(Date d) { System.out.println(d); } public static void m5() { System.out.println("m5"); } public static void m6(String[] strs) { System.out.println(strs.length); } public static void main(String[] args) { System.out.println("main"); } }
package com.cj.test; import java.lang.reflect.Method; import java.util.Date; import org.junit.Test; public class Demo2 { @Test//public void m1() public void test1() throws Exception{ Class clazz = Class.forName("com.cj.test.Person"); Person p = (Person)clazz.newInstance(); Method m = clazz.getMethod("m1", null); m.invoke(p, null); } @Test//public void m2(String name) public void test2() throws Exception{ Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Method m = clazz.getMethod("m2", String.class); m.invoke(p, "張三"); } @Test//public String m3(String name,int age) public void test3() throws Exception{ Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Method m = clazz.getMethod("m3", String.class,int.class); String returnValue = (String)m.invoke(p, "張三",23); System.out.println(returnValue); } @Test//private void m4(Date d) public void test4() throws Exception{ Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Method m = clazz.getDeclaredMethod("m4", Date.class); m.setAccessible(true); m.invoke(p,new Date()); } @Test//public static void m5() public void test5() throws Exception{ Class clazz = Person.class; Method m = clazz.getMethod("m5", null); m.invoke(null,null); } @Test//private static void m6(String[] strs) public void test6() throws Exception{ Class clazz = Person.class; Method m = clazz.getDeclaredMethod("m6",String[].class); m.setAccessible(true); m.invoke(null,(Object)new String[]{"a","b"}); } @Test public void test7() throws Exception{ Class clazz = Person.class; Method m = clazz.getMethod("main",String[].class); m.invoke(null,new Object[]{new String[]{"a","b"}}); } }
*****注意:看下上邊代碼里test6和test7的invoke方法里傳的參數(shù)和其他的有點(diǎn)不一樣
這是因?yàn)?jdk1.4和jdk1.5處理invoke方法有區(qū)別
1.5:public Object invoke(Object obj,Object…args)
1.4:public Object invoke(Object obj,Object[] args)
由于JDK1.4和1.5對invoke方法的處理有區(qū)別, 所以在反射類似于main(String[] args) 這種參數(shù)是數(shù)組的方法時(shí)需要特殊處理
啟動Java程序的main方法的參數(shù)是一個(gè)字符串?dāng)?shù)組,即public static void main(String[] args),通過反射方式來調(diào)用這個(gè)main方法時(shí),如何為invoke方法傳遞參數(shù)呢?按jdk1.5的語法,整個(gè)數(shù)組是一個(gè)參數(shù),而按jdk1.4的語法,數(shù)組中的每個(gè)元素對應(yīng)一個(gè)參數(shù),當(dāng)把一個(gè)字符串?dāng)?shù)組作為參數(shù)傳遞給invoke方法時(shí),javac會到底按照哪種語法進(jìn)行處理呢?jdk1.5肯定要兼容jdk1.4的語法,會按jdk1.4的語法進(jìn)行處理
,即把數(shù)組打散成為若干個(gè)單獨(dú)的參數(shù)。所以,在給main方法傳遞參數(shù)時(shí),不能使用代碼mainMethod.invoke(null,new String[]{“xxx”}),javac只把它當(dāng)作jdk1.4的語法進(jìn)行理解,而不把它當(dāng)作jdk1.5的語法解釋,因此會出現(xiàn)參數(shù)個(gè)數(shù)不對的問題。
上述問題的解決方法:
- (1)mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
這種方式,由于你傳的是一個(gè)數(shù)組的參數(shù),所以為了向下兼容1.4的語法,javac遇到數(shù)組會給你拆開成多個(gè)參數(shù),但是由于咱們這個(gè)Object[ ] 數(shù)組里只有一個(gè)元素值,所以就算它拆也沒關(guān)系
- (2)mainMethod.invoke(null,(Object)new String[]{"xxx"});
這種方式相當(dāng)于你傳的參數(shù)是一個(gè)對象,而不是數(shù)組,所以就算是按照1.4的語法它也不會拆,所以問題搞定
編譯器會作特殊處理,編譯時(shí)不把參數(shù)當(dāng)作數(shù)組看待,也就不會數(shù)組打散成若干個(gè)參數(shù)了
對上邊的描述進(jìn)行一下總結(jié):在反射方法時(shí),如果方法的參數(shù)是一個(gè)數(shù)組,考慮到向下兼容問題,會按照J(rèn)DK1.4的語法來對待(JVM會把傳遞的數(shù)組參數(shù)拆開,拆開就會報(bào)參數(shù)的個(gè)數(shù)不匹配的錯(cuò)誤)
解決辦法:防止JVM拆開你的數(shù)組
- 方式一:把數(shù)組看做是一個(gè)Object對象
- 方式二:重新構(gòu)建一個(gè)Object數(shù)組,那個(gè)參數(shù)數(shù)組作為唯一的元素存在。
2.3反射類中的屬性字段
package com.cj.test; import java.util.Date; public class Person { public String name="李四"; private int age = 18; public static Date time; public int getAge() { return age; } public Person(){ System.out.println("默認(rèn)的無參構(gòu)造方法執(zhí)行了"); } public Person(String name){ System.out.println("姓名:"+name); } public Person(String name,int age){ System.out.println(name+"="+age); } private Person(int age){ System.out.println("年齡:"+age); } public void m1() { System.out.println("m1"); } public void m2(String name) { System.out.println(name); } public String m3(String name,int age) { System.out.println(name+":"+age); return "aaa"; } private void m4(Date d) { System.out.println(d); } public static void m5() { System.out.println("m5"); } public static void m6(String[] strs) { System.out.println(strs.length); } public static void main(String[] args) { System.out.println("main"); } }
package com.cj.test; import java.lang.reflect.Field; import java.util.Date; import org.junit.Test; public class Demo3 { //public String name="李四"; @Test public void test1() throws Exception{ Class clazz = Person.class; Person p = (Person)clazz.newInstance(); Field f = clazz.getField("name"); String s = (String)f.get(p); System.out.println(s); //更改name的值 f.set(p, "王六"); System.out.println(p.name); } @Test//private int age = 18; public void test2() throws Exception{ Class clazz = Person.class; Person p = (Person)clazz.newInstance(); Field f = clazz.getDeclaredField("age"); f.setAccessible(true); int age = (Integer)f.get(p); System.out.println(age); f.set(p, 28); age = (Integer)f.get(p); System.out.println(age); } @Test//public static Date time; public void test3() throws Exception{ Class clazz = Person.class; Field f = clazz.getField("time"); f.set(null, new Date()); System.out.println(Person.time); } }
以上就是自己對Java中反射的一些學(xué)習(xí)總結(jié),歡迎大家留言一起學(xué)習(xí)、討論
看完上邊有關(guān)反射的東西, 對常用框架里的配置文件是不是有點(diǎn)思路了
上邊是Spring配置文件里的常見的bean配置,這看起來是不是可以用反射很輕易的就可以實(shí)現(xiàn):解析xml然后把xml里的內(nèi)容作為參數(shù),利用反射創(chuàng)建對象。
以上所述是小編給大家介紹的Java基礎(chǔ)篇之反射機(jī)制詳解,希望對大家有所幫助。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
java跳出循環(huán)的三種方式總結(jié)(break語句、continue語句和return語句)
在實(shí)際編程中,有時(shí)需要在條件語句匹配的時(shí)候跳出循環(huán),下面這篇文章主要給大家介紹了關(guān)于java跳出循環(huán)的三種方式,其中包括break語句、continue語句和return語句的相關(guān)資料,需要的朋友可以參考下2023-03-03java面試LruCache?和?LinkedHashMap及算法實(shí)現(xiàn)
這篇文章主要為大家介紹了java面試LruCache?和?LinkedHashMap及算法實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02java中判斷字段真實(shí)長度的實(shí)例(中文2個(gè)字符,英文1個(gè)字符)
下面小編就為大家?guī)硪黄猨ava中判斷字段真實(shí)長度的實(shí)例(中文2個(gè)字符,英文1個(gè)字符)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理解讀
這篇文章主要介紹了SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02SpringBoot前后端分離實(shí)現(xiàn)個(gè)人博客系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了使用springboot+mybatis+前端vue,使用前后端分離架構(gòu)實(shí)現(xiàn)的個(gè)人博客系統(tǒng),感興趣的小伙伴可以動手嘗試一下2022-06-06Java數(shù)據(jù)結(jié)構(gòu)之線段樹詳解
線段樹是一種二叉搜索樹,與區(qū)間樹相似,它將一個(gè)區(qū)間劃分成一些單元區(qū)間,每個(gè)單元區(qū)間對應(yīng)線段樹中的一個(gè)葉結(jié)點(diǎn)。本文將介紹線段樹的Java實(shí)現(xiàn)代碼,需要的可以參考一下2022-01-01最長重復(fù)子數(shù)組 findLength示例詳解
今天給大家分享一道比較常問的算法面試題,最長重復(fù)子數(shù)組 findLength,文中給大家分享解題思路,結(jié)合示例代碼介紹的非常詳細(xì),需要的朋友參考下吧2023-08-08