《重学Java系列》之 反射(上)
不詩(shī)意的女程序媛不是好廚師~
轉(zhuǎn)載請(qǐng)注明出處,From李詩(shī)雨—https://blog.csdn.net/cjm2484836553/article/details/103350829】
《重學(xué)Java系列》之 反射(上)
- 1.反射是什么?
- 2.了解Class類
- (1). Class類是什么?
- (2). 獲取Class對(duì)象的3種方式
- (3). Class類的常用方法
- 3.反射的基本使用
- (1).Constructor的基本使用
- (2).Field的基本使用
- (3).Method的基本使用
- 小結(jié)與回顧
今天我們來(lái)學(xué)習(xí)學(xué)習(xí)反射,如果單純講反射的使用的話,其實(shí)一點(diǎn)也不復(fù)雜,我們只需要對(duì)照相應(yīng)的API進(jìn)行調(diào)用就可以了。但是要想對(duì)反射有較深層的認(rèn)識(shí)和感悟,那就需要我們多看源碼。從真實(shí)的實(shí)戰(zhàn)中,我們才能體會(huì)其精髓。
所以,對(duì)于反射的學(xué)習(xí)我將分為上、中、下 3篇來(lái)總結(jié)。上篇講思維導(dǎo)圖中的前三點(diǎn):反射是什么,Class類 和 反射的基本使用。中篇從動(dòng)態(tài)代理的源碼中去體會(huì)反射,下篇從Retrofit源碼中再去體會(huì)反射。如此步步深入才能學(xué)到點(diǎn)東西。
今天講的是反射基礎(chǔ),但是不要小瞧基礎(chǔ)哦,沒有基礎(chǔ)什么都白搭。話不多說(shuō),就讓我們開始吧~ ?
1.反射是什么?
說(shuō)到反射,可能大家即陌生又熟悉。為什么這么說(shuō)呢?
其實(shí),反射在我們自己平時(shí)的開發(fā)過(guò)程中用到的并不多,因此它是陌生的。但是,在我們使用到的很多框架代碼中,一旦你要去閱讀他們的源碼,就會(huì)經(jīng)常發(fā)現(xiàn)反射的身影,所以說(shuō)它又是十分熟悉的。
那到底什么是反射呢?
反射就是在運(yùn)行時(shí)才知道要操作的類是什么,并且可以在運(yùn)行時(shí)獲取類的完整構(gòu)造,并調(diào)用對(duì)應(yīng)的方法。
這么官方的解釋是不是聽著還有點(diǎn)不太明白?沒事,我再來(lái)?yè)Q種方法跟大家說(shuō)。
咱們先不說(shuō)反射,咱們來(lái)先說(shuō) 【正】。
大家回憶一下,【正】常的 我們?cè)趺磥?lái)實(shí)例化一個(gè)對(duì)象?
是不是通過(guò) new,直接new一個(gè)對(duì)象出來(lái),然后我就可訪問(wèn)類的成員、調(diào)用類的方法了。
那 【反】 射 就不一樣了,它是一開始并不知道我要初始化的類是什么?既然都不知道類是什么,那當(dāng)然不能用 new 關(guān)鍵字來(lái) 正面的 直接的創(chuàng)建對(duì)象了。那我們?cè)撛趺崔k呢?這種情況下我們就可以使用 JDK 提供的反射 API 進(jìn)行反射調(diào)用。
//反射的使用 【反】Class personClass = Person.class;//要先獲取對(duì)應(yīng)的Class對(duì)象Person reflePerson=(Person)personClass.newInstance();//通過(guò)class得到類的實(shí)例reflePerson.say("哈哈哈哈哈哈"); //然后才可以對(duì)對(duì)象進(jìn)行其他操作這個(gè)時(shí)候你是不是會(huì)有疑問(wèn):
我們平時(shí)寫代碼都知道是什么類啊,直接new個(gè)對(duì)象出來(lái)就好了。什么時(shí)候才會(huì)出現(xiàn)不知道new的是什么類呢?
你還別說(shuō), 反射在我們平時(shí)自己代碼中還真的基本用不到,它是在我們寫框架代碼的時(shí)候才會(huì)用到。如果是框架的開發(fā)人員,或者是在我們閱讀源碼的時(shí)候那反射就用的多了。
大家在用Retrofit網(wǎng)絡(luò)請(qǐng)求的框架的時(shí)候,有沒有想過(guò):這個(gè)框架它在創(chuàng)建的時(shí)候,它是怎么知道將來(lái)我們的調(diào)用者會(huì)創(chuàng)建什么樣的類呢?他顯然是不知道的。那它怎么來(lái)new出我們調(diào)用者所需要的對(duì)象呢?這里我們就需要用到反射了。
所以反射的定義我這么這個(gè)時(shí)候再看一下,是不是就明白了許多呢:
反射是在運(yùn)行時(shí)才知道要操作的類是什么,并且在運(yùn)行時(shí)來(lái)獲取類的完整構(gòu)造,并調(diào)用對(duì)應(yīng)的方法。
Reflection(反射) 是Java被視為動(dòng)態(tài)語(yǔ)言的關(guān)鍵,反射機(jī)制允許程序在執(zhí)行期借助于Reflection API取得任何類的內(nèi)部信息,并能直接操作任意對(duì)象的內(nèi)部屬性及方法。
我們來(lái)歸納一下Java反射機(jī)制主要提供的功能:
- 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象
- 在運(yùn)行時(shí)獲取任意一個(gè)類所具有的成員變量和方法
- 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法(屬性)
雖然我說(shuō)的比較多,但相信經(jīng)過(guò)這樣的反復(fù)說(shuō)說(shuō)說(shuō)之后,你看一遍就可以懂了。
大家都知道Java 是一門面向?qū)ο蟮恼Z(yǔ)言。在面向?qū)ο蟮氖澜缋?#xff0c;萬(wàn)事萬(wàn)物皆對(duì)象。
我就又要問(wèn)問(wèn)大家了:既然萬(wàn)事萬(wàn)物皆對(duì)象,那我們寫的每一個(gè)類(比如說(shuō)Person類),是不是應(yīng)該也是對(duì)象呀。那它又是誰(shuí)的對(duì)象呢?
答案是:它是Class類的對(duì)象。
好下面我們就來(lái)說(shuō)說(shuō)Class類。
2.了解Class類
(1). Class類是什么?
上面我們說(shuō)了,我們寫的每一個(gè)類都可以看成一個(gè)對(duì)象,是java.lang.Class 類的對(duì)象。
那么Class到底是什么呢?
Class它是一個(gè)類,它封裝了當(dāng)前的類里面所包括的所有信息。
一個(gè)類有哪些信息呢?
一個(gè)類中有屬性,方法,構(gòu)造器等。
比如說(shuō)有一個(gè)Person類,一個(gè)Fruit類,一個(gè)Animal類,這些都是不同的類,現(xiàn)在我們需要一個(gè)類,用來(lái)描述這些類,這就是Class。所以Class是用來(lái)描述類的類。
你可以這樣理解:Class類是一個(gè)對(duì)象照鏡子的結(jié)果,對(duì)象可以看到自己有哪些屬性,方法,構(gòu)造器,實(shí)現(xiàn)了哪些接口等等。
對(duì)于每個(gè)類而言,JRE 都為其保留一個(gè)不變的 Class 類型的對(duì)象。一個(gè) Class 對(duì)象包含了特定某個(gè)類的有關(guān)信息。
而且大家需要知道:一個(gè)類(而不是一個(gè)對(duì)象)在 JVM 中只會(huì)有一個(gè)Class實(shí)例。
我們可以new出這個(gè)類的很多個(gè)對(duì)象,但是這個(gè)類在JDK中只會(huì)有一個(gè)Class對(duì)象。
比如說(shuō):一個(gè)Person類,它可以new出很多個(gè)對(duì)象。
但是Person類對(duì)應(yīng)的Class類的實(shí)例只有唯一一個(gè)。
(2). 獲取Class對(duì)象的3種方式
那么,我們要怎么樣才能獲取到Class對(duì)象呢?
這里有3種方式:
- 方式1:通過(guò)類名獲取 --> 類名.class
- 方式2:通過(guò)對(duì)象獲取 --> 對(duì)象名.getClass()
- 方式3:通過(guò)全類名獲取 --> Class.forName(全類名,即包名+類名)
以Person類為例:
(3). Class類的常用方法
3.反射的基本使用
上面我們已經(jīng)知道了如何拿到class對(duì)象。
但是純粹的拿到class對(duì)象,其實(shí)是沒有什么實(shí)際含義的,我們真正的目的是為了能夠設(shè)置它的屬性,調(diào)用它的方法。
那我們?nèi)绾稳カ@取和修改它的屬性、調(diào)用它的方法呢?
其實(shí)像構(gòu)造器,屬性,方法這些信息在Java中都有對(duì)應(yīng)的類來(lái)表示。
下面就讓我們分別來(lái)看看該怎么用:
首先我們定義一個(gè)Person類:
/*** @author shiyu* @create 2019-12-02 9:50*/ public class Person {//私有字段private String name;private int age;//公有字段public String sex;//無(wú)參構(gòu)造方法public Person() {}//有參構(gòu)造方法public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}//普通公有方法public void say(String message){System.out.println(message);}//普通私有方法private void secret(){System.out.println("我是Person的私有方法");} }讓我們來(lái)提取一下Person類的信息:
(1).Constructor的基本使用
使用要點(diǎn):
- getConstructors() :可以獲取該類的所有構(gòu)造器
- getConstructor() :可以獲取某一個(gè)指定參數(shù)的構(gòu)造器,需要參數(shù)列表。
而且需要注意 傳的參數(shù)列表必須嚴(yán)格的一樣,否則會(huì)報(bào)NoSuchMethodException。
比如 【clazz0.getConstructor(String.class, int.class);】//如果不傳int.class,而傳Integer.class是不行的. - 得到構(gòu)造器后,再調(diào)用構(gòu)造器的 newInstance() 方法可以創(chuàng)建對(duì)象
直接上代碼來(lái)演示使用:
//------------------------Constructor的基本使用-----------------------String className0 = "reflect.Person";Class<Person> clazz0 = (Class<Person>) Class.forName(className0);//getConstructors()---獲取全部Constructor對(duì)象Constructor<Person>[] constructors = (Constructor<Person>[]) clazz0.getConstructors();for(Constructor<Person> constructor: constructors){System.out.println("getConstructors()獲取到的構(gòu)造器:"+constructor);}System.out.println();//getConstructor()----獲取某一個(gè)Constructor 對(duì)象,需要參數(shù)列表// //注意此處傳的參數(shù)列表必須嚴(yán)格的一樣,否則會(huì)報(bào)有可能NoSuchMethodExceptionConstructor<Person> constructor = clazz0.getConstructor(String.class, int.class);//如果傳Integer.class是不行的.System.out.println("getConstructor()獲取到的構(gòu)造器:"+constructor);//調(diào)用構(gòu)造器的 newInstance() 方法創(chuàng)建對(duì)象Person malegod=constructor.newInstance("夏夏夏",26);malegod.say("我負(fù)責(zé)指導(dǎo)詩(shī)雨學(xué)習(xí)!");System.out.println();我們來(lái)看下打印結(jié)果:
(2).Field的基本使用
使用要點(diǎn):
- getDeclaredFields():可以獲取 自己的 所有字段( 包括私有的),但不能獲取父類字段
- getDeclaredField(name)—獲取指定字段
- 如果獲取的字段是私有的,不管是讀還是寫,都要先 field.setAccessible(true);才可以。否則會(huì)報(bào):IllegalAccessException。
老規(guī)矩直接上代碼:
//-------------Field的基本使用--------------------Person goddess=new Person("賈玲",18);//先new個(gè)女神String className = "reflect.Person";Class clazz = Class.forName(className);//getDeclaredFields()---獲取 自己的 公有和私有的所有字段,但不能獲取父類字段Field[] fields = clazz.getDeclaredFields();for(Field field: fields){System.out.println("getDeclaredFields()獲取的字段:"+ field.getName());}System.out.println();//getDeclaredField()---獲取指定字段,傳入字段的名稱Field field=clazz.getDeclaredField("name");Field field2 = clazz.getDeclaredField("sex");//獲取的字段是私有的,不管是讀還是寫都要先設(shè)置 field.setAccessible(true);field.setAccessible(true);System.out.println("getDeclaredField()獲取的字段:"+field.getName());//field.getName()是讀操作,所以在此之前要設(shè)置field.setAccessible(true)System.out.println("getDeclaredField()獲取的字段:"+field2.getName());//"獲取指定字段的值"Object val = field.get(goddess);Object val2 = field2.get(goddess);System.out.println(field.getName()+"="+val);System.out.println(field2.getName()+"="+val2);//修改指定字段的為指定的值field.set(goddess,"郭德綱"); //相當(dāng)于是寫操作System.out.println(field.getName()+"="+goddess.getName());//這個(gè)時(shí)候再打印,女神的名字就變成了郭德綱了看一下打印結(jié)果:
(3).Method的基本使用
使用要點(diǎn):
- getMethods():可以獲取clazz對(duì)應(yīng)類的所有公有方法,包括從父類繼承來(lái)的方法,私有方法不能得到。
- getDeclaredMethods():只獲取當(dāng)前類的所有方法,包括私有方法
- getDeclaredMethod():獲取指定的方法,需要參數(shù)名稱和參數(shù)列表,無(wú)參則不需要寫。
也要注意 傳入的參數(shù)類型要嚴(yán)格一致。 - 私有方法的執(zhí)行,必須在調(diào)用invoke之前加上一句method.setAccessible(true)
上代碼:
//-------------Method的基本使用--------------------Person goddess=new Person("賈玲",18);//有一個(gè)女神String className = "reflect.Person";Class clazz = Class.forName(className);//getMethods()---獲取clazz對(duì)應(yīng)類所有公有方法,包括從父類繼承來(lái)的方法Method[] methods= clazz.getMethods();for(Method method:methods){System.out.println("getMethods()獲取的方法: "+method.getName()+"()");}System.out.println("");//getDeclaredMethods()---只獲取當(dāng)前類的所有方法,包括私有方法methods = clazz.getDeclaredMethods();for(Method method:methods){System.out.println(" getDeclaredMethods()獲取的方法:"+method.getName()+"()");}System.out.println("");//getDeclaredMethod()---獲取指定的方法,需要參數(shù)名稱和參數(shù)列表,無(wú)參則不需要寫//注意 是 參數(shù)寫成int.classMethod method = clazz.getDeclaredMethod("setAge", int.class);//獲取的是方法public void setAge(int age) { }System.out.println("getDeclaredMethod()獲取的指定方法:"+method);System.out.println("");//第一個(gè)參數(shù)表示執(zhí)行哪個(gè)對(duì)象的方法,剩下的參數(shù)是執(zhí)行方法時(shí)需要傳入的參數(shù)"method.invoke(goddess,25);System.out.println("此時(shí)女神的年齡是:"+goddess.getAge());System.out.println();/*私有方法的執(zhí)行,必須在調(diào)用invoke之前加上一句method.setAccessible(true);*/method = clazz.getDeclaredMethod("secret");System.out.println(method);method.setAccessible(true);//在訪問(wèn)私有域時(shí)同樣method.invoke(goddess);//執(zhí)行私有方法打印結(jié)果大家可以對(duì)照Person類的思維導(dǎo)圖看:
小結(jié)與回顧
通過(guò)上面的學(xué)習(xí)我們知道了
獲取class對(duì)象有3種方式:
- 1:通過(guò)類名獲取 --> 類名.class
- 2.通過(guò)對(duì)象獲取 --> 對(duì)象名.getClass()
- 3.通過(guò)全類名獲取 --> Class.forName(全類名)
那么請(qǐng)問(wèn)大家 獲取一個(gè)類的實(shí)例對(duì)象方法 有幾種呢?
其實(shí)是有3種的:
- 直接new出來(lái)
- 2.獲取class對(duì)象后,調(diào)用newInstance()方法
3.通過(guò)反射拿到這個(gè)類的構(gòu)造器之后,再通過(guò)構(gòu)造器的newInstance獲得。
Constructor<Person> constructor = clazz0.getConstructor(String.class, int.class);Person malegod=constructor.newInstance("夏夏夏",26);今天的內(nèi)容其實(shí)很簡(jiǎn)單,相信大家看了一遍之后就懂了會(huì)了,有了這些基礎(chǔ)之后,下次我們?cè)倏丛创a就會(huì)順利很多了~
積累點(diǎn)滴,做好自己~
總結(jié)
以上是生活随笔為你收集整理的《重学Java系列》之 反射(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【Python符号】python里的@符
- 下一篇: Java花鼓保养,自行车花鼓正确保养方法