類(lèi)加載
類(lèi)加載負(fù)責(zé)加載編譯后的class文件(字節(jié)碼文件)到JVM當(dāng)中。
- 在JRE中,類(lèi)加載器主要分為以下幾種:
-
引導(dǎo)類(lèi)加載器(Bootstrap)- 它本身使用C/C++語(yǔ)言實(shí)現(xiàn)的,負(fù)責(zé)加載Java的核心類(lèi)庫(kù),在jre\lib目錄中,當(dāng)中包括如rt.jar,這些都是Java自帶的核心類(lèi)庫(kù),必須由它來(lái)完成加載。
-
拓展/擴(kuò)展類(lèi)加載器(Extension)- 這個(gè)加載器就是由Java語(yǔ)言實(shí)現(xiàn),負(fù)責(zé)加載jre\lib\ext目錄下的類(lèi)庫(kù),這個(gè)目錄下的類(lèi)庫(kù)都是一些擴(kuò)展類(lèi)。
-
應(yīng)用程序/系統(tǒng)類(lèi)加載器(Application)- 這個(gè)類(lèi)加載器同樣使用Java語(yǔ)言實(shí)現(xiàn),它主要負(fù)責(zé)加載classpath下面的所有類(lèi)庫(kù),通常我們編寫(xiě)的Java類(lèi)都是由這個(gè)類(lèi)加載器完成加載。
-
- 三個(gè)類(lèi)加載器的初始化過(guò)程:當(dāng)程序運(yùn)行時(shí),首先會(huì)初始化引導(dǎo)類(lèi)加載器,它就負(fù)責(zé)創(chuàng)建和初始化擴(kuò)展類(lèi)加載器,當(dāng)擴(kuò)展類(lèi)加載器完成初始化之后,又負(fù)責(zé)創(chuàng)建和初始化系統(tǒng)類(lèi)加載器。
- 這些類(lèi)加載器協(xié)同起來(lái)完成整個(gè)類(lèi)加載的過(guò)程,因此這些類(lèi)加載器的加載模式是基于“雙親委托模型”。
- 雙親委托模型
舉例說(shuō)明
- 當(dāng)我們編寫(xiě)一個(gè)Java類(lèi)時(shí),首先負(fù)責(zé)加載這個(gè)類(lèi)的加載器是系統(tǒng)類(lèi)加載器,但是它不會(huì)立馬就去執(zhí)行加載,而是先把這個(gè)任務(wù)交給父加載器(擴(kuò)展類(lèi)加載器),而擴(kuò)展類(lèi)加載器同樣也會(huì)將這個(gè)任務(wù)交給父加載器(引導(dǎo)類(lèi)加載器),最終當(dāng)引導(dǎo)類(lèi)加載器不能去加載這個(gè)類(lèi)的時(shí)候(也就是在自己加載職責(zé)范圍找不到的時(shí)候),又會(huì)將這個(gè)任務(wù)交回給子加載器。以此類(lèi)推,最終我們編寫(xiě)的類(lèi)都會(huì)配置在classpath環(huán)境中,所以,這個(gè)類(lèi)的加載任務(wù)還是回到系統(tǒng)類(lèi)加載器來(lái)完成。
public class Test {
String name;
public void say(){
System.out.println("Hello Word");
}
// 程序的入口的方法,和具體類(lèi)無(wú)關(guān)
public static void main(String[] args) {
Test t = new Test();
t.say();
// 得到當(dāng)前的類(lèi)加載器ApplicationClassLoader
ClassLoader cl = Test.class.getClassLoader();
System.out.println(cl);
// 得到ApplicationClassLoader的父類(lèi)加載器ExtensionClassLoader
ClassLoader extCl = cl.getParent();
System.out.println(extCl);
// 得到ExtensionClassLoader的父類(lèi)加載器BootstrapClassLoader
// 由于BootstrapClassLoader是用C/C++語(yǔ)言編寫(xiě)的,在java中無(wú)法直接使用
// 所以才會(huì)返回一個(gè)null
ClassLoader bootCl = extCl.getParent();
System.out.println(bootCl);
}
}
- 當(dāng)一個(gè)class文件最終加載到j(luò)vm之后,就表示類(lèi)加載這個(gè)階段已經(jīng)全部完成。接下來(lái)就是對(duì)整個(gè)class文件的內(nèi)容進(jìn)行解析和做內(nèi)存的分配。
內(nèi)存分配
當(dāng)JVM運(yùn)行起來(lái)的時(shí)候就會(huì)給內(nèi)存劃分空間,那么這塊空間稱(chēng)之為運(yùn)行時(shí)數(shù)據(jù)區(qū)。
(備注:當(dāng)一個(gè)Java源程序編譯成class字節(jié)碼文件之后,字節(jié)碼文件里存放的都是二進(jìn)制的匯編命令,當(dāng)程序運(yùn)行的時(shí)候,JVM會(huì)將這個(gè)二進(jìn)制的命令逐行解釋?zhuān)唤oCPU去執(zhí)行)
- 運(yùn)行時(shí)數(shù)據(jù)區(qū)將劃分為以下幾塊內(nèi)容:
-
棧- 每一個(gè)線(xiàn)程運(yùn)行起來(lái)的時(shí)候就會(huì)對(duì)應(yīng)一個(gè)棧(線(xiàn)程棧),棧當(dāng)中存放的數(shù)據(jù)是被當(dāng)前線(xiàn)程所獨(dú)有的。而棧當(dāng)中存放的是棧幀,當(dāng)線(xiàn)程調(diào)用一個(gè)方法的時(shí)候,就會(huì)形成一個(gè)棧幀,并將這個(gè)棧幀進(jìn)行壓棧操作,當(dāng)方法執(zhí)行完之后就會(huì)將這個(gè)棧幀進(jìn)行出棧操作。這個(gè)棧幀里面包括(局部變量、操作數(shù)棧、指向當(dāng)前方法對(duì)應(yīng)類(lèi)的常量池引用、方法的返回地址等信息)。
(備注:由于局部變量都是存放在棧中,而每一個(gè)線(xiàn)程都對(duì)應(yīng)自己的線(xiàn)程棧,因此局部變量是線(xiàn)程安全的,不會(huì)才產(chǎn)生資源共享的情況。) - 遞歸棧
- 每一個(gè)線(xiàn)程運(yùn)行起來(lái)的時(shí)候就會(huì)對(duì)應(yīng)一個(gè)棧(線(xiàn)程棧),棧當(dāng)中存放的數(shù)據(jù)是被當(dāng)前線(xiàn)程所獨(dú)有的。而棧當(dāng)中存放的是棧幀,當(dāng)線(xiàn)程調(diào)用一個(gè)方法的時(shí)候,就會(huì)形成一個(gè)棧幀,并將這個(gè)棧幀進(jìn)行壓棧操作,當(dāng)方法執(zhí)行完之后就會(huì)將這個(gè)棧幀進(jìn)行出棧操作。這個(gè)棧幀里面包括(局部變量、操作數(shù)棧、指向當(dāng)前方法對(duì)應(yīng)類(lèi)的常量池引用、方法的返回地址等信息)。
-
本地方法棧- 本地方法棧的機(jī)制和棧的機(jī)制類(lèi)似,區(qū)別在于,棧是運(yùn)行Java所實(shí)現(xiàn)的方法,而本地方法棧是運(yùn)行的本地方法(Native Method)。所謂的本地方法指的是在本地jvm中需要調(diào)用非Java語(yǔ)言所實(shí)現(xiàn)的方法,例如c語(yǔ)言。在JVM的規(guī)范中,其實(shí)沒(méi)有強(qiáng)制性要求實(shí)現(xiàn)方一定要?jiǎng)澐殖霰镜胤椒5暮途唧w的實(shí)現(xiàn),這一部分可以根據(jù)實(shí)現(xiàn)方具體要求來(lái)實(shí)現(xiàn)。因此在HotSport虛擬機(jī)的實(shí)現(xiàn)中就將方法棧和本地方法棧二合為一。
- 本地方法棧
-
程序計(jì)數(shù)器- 程序計(jì)數(shù)器也可以稱(chēng)之為PC寄存器。它主要用于存放當(dāng)前程序下一條將要執(zhí)行的指令地址。CPU會(huì)根據(jù)這個(gè)地址找到對(duì)應(yīng)的指令來(lái)執(zhí)行。通俗的講就是指令緩存。這個(gè)寄存器是有JVM內(nèi)部實(shí)現(xiàn)的,并不是物理概念上的寄存器,但是JVM在實(shí)現(xiàn)功能的邏輯上是相同的。
-
堆- 堆內(nèi)存中主要存放創(chuàng)建的對(duì)象以及數(shù)組。 堆內(nèi)存是可以被多個(gè)線(xiàn)程所共享的一塊區(qū)域,因此多個(gè)線(xiàn)程棧都可以去訪(fǎng)問(wèn)同一塊堆的內(nèi)存區(qū)域。堆里面的每一對(duì)象都存放了該實(shí)例的實(shí)例變量。
- 當(dāng)在方法中定義了一個(gè)局部變量,如果這個(gè)變量是基本數(shù)據(jù)類(lèi)型,那么這個(gè)變量的值就直接存放在棧中,如果這個(gè)變量是引用數(shù)據(jù)類(lèi)型,那么這個(gè)對(duì)象變量就存放在堆內(nèi)存中,而棧中存放的是一個(gè)指向堆內(nèi)存中這個(gè)對(duì)象的首地址。
(備注:Java中除了8個(gè)基本數(shù)據(jù)類(lèi)型以外的所有類(lèi)型都是引用數(shù)據(jù)類(lèi)型) - 引用
- 引用
- 更改
- 更改
- 數(shù)組
- 數(shù)組
- 循環(huán)
- 循環(huán)
- 實(shí)例變量和靜態(tài)變量的區(qū)別:
-
實(shí)例變量:實(shí)例變量是隨著對(duì)象的創(chuàng)建而創(chuàng)建,而實(shí)例是存放在堆中,所以實(shí)例變量自然也就跟實(shí)例一并保存在堆內(nèi)存。只要?jiǎng)?chuàng)建多少個(gè)實(shí)例,就會(huì)有多少份實(shí)例變量。當(dāng)實(shí)例被回收的時(shí)候,實(shí)例變量也隨之而銷(xiāo)毀。 -
靜態(tài)變量:靜態(tài)變量也叫類(lèi)變量,它是在類(lèi)加載的時(shí)候就已經(jīng)初始化好,并存放在方法區(qū),并且只有一份,所以它是被多個(gè)實(shí)例所共享的一個(gè)變量。
-
-
方法區(qū)- 方法區(qū)在JVM中也是一個(gè)非常重要的一塊內(nèi)存區(qū)域,它和堆一樣,是可以被多個(gè)線(xiàn)程所共享的一塊區(qū)域。這個(gè)區(qū)域中主要存放了每一個(gè)加載的class文件信息。
在一個(gè)class文件中主要包含魔數(shù)(代碼中出現(xiàn)但沒(méi)有解釋的數(shù)字常量或字符串)(用來(lái)確定是否是一個(gè)class文件)、常量池(常量池在下面會(huì)有完整說(shuō)明)、訪(fǎng)問(wèn)標(biāo)志(當(dāng)前的class是類(lèi)還是接口,是否是抽象類(lèi),
是否是public修飾,是否使用了final修飾等描述信息...)、字段表集合信息(使用什么訪(fǎng)問(wèn)修飾符、是實(shí)例變量還是靜態(tài)變量,是否用final修飾等描述信息...)、
方法表集合信息(訪(fǎng)問(wèn)修飾符,是否靜態(tài)方法,是否用final修飾,是否用了synchronized修飾,是否是native方法...)等內(nèi)容。當(dāng)一個(gè)類(lèi)加載器加載一個(gè)class文件的時(shí)候,
會(huì)根據(jù)這個(gè)class文件的內(nèi)容創(chuàng)建一個(gè)Class對(duì)象,而這個(gè)Class對(duì)象就包括了上述的這些內(nèi)容。后續(xù)要?jiǎng)?chuàng)建這個(gè)類(lèi)的所有實(shí)例,都是通過(guò)這個(gè)Class對(duì)象創(chuàng)建出來(lái)的。 - 方法區(qū)
- 方法區(qū)在JVM中也是一個(gè)非常重要的一塊內(nèi)存區(qū)域,它和堆一樣,是可以被多個(gè)線(xiàn)程所共享的一塊區(qū)域。這個(gè)區(qū)域中主要存放了每一個(gè)加載的class文件信息。
-
常量池- 常量池也是方法區(qū)中的一部分,它存放的內(nèi)容是class文件中最重要的資源,JVM為每一個(gè)class對(duì)象都維護(hù)一個(gè)常量池。它主要存儲(chǔ)兩種類(lèi)型的常量。
- 字面常量
- 字面常量通常就是在Java中定義的字面量值,如:int i =1,這個(gè)1就是字面量;String s = ("abc"),這個(gè)abc就是字面量?;蛘呤褂胒inal修飾的常量值等等。
- 符號(hào)引用
- 符號(hào)引用主要包括類(lèi)和接口的完整類(lèi)名、屬性字段的名稱(chēng)和描述符、方法名稱(chēng)和描述符等信息
- 字面常量
- 常量池
- 常量池也是方法區(qū)中的一部分,它存放的內(nèi)容是class文件中最重要的資源,JVM為每一個(gè)class對(duì)象都維護(hù)一個(gè)常量池。它主要存儲(chǔ)兩種類(lèi)型的常量。
-
- 在Java當(dāng)中,8個(gè)基本數(shù)據(jù)類(lèi)型都有對(duì)應(yīng)的包裝類(lèi)型,而大部分包裝類(lèi)型都實(shí)現(xiàn)了常量池的技術(shù),除了
Double和Float類(lèi)。
(備注說(shuō)明:在JDK8之后,方法區(qū)已經(jīng)取消,方法區(qū)被一個(gè)叫MetaSpace,它和堆合并到一起管理) - 內(nèi)存運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 內(nèi)存運(yùn)行時(shí)數(shù)據(jù)區(qū)
扯了好多Java虛擬機(jī)的內(nèi)容,也沒(méi)講多深,因?yàn)檫@里主要的目的是為了大家方便理解Java反射機(jī)制,下面正式進(jìn)入正題
Class對(duì)象
當(dāng)ClassLoader加載一個(gè)class文件到JVM的時(shí)候,會(huì)自動(dòng)創(chuàng)建一個(gè)該類(lèi)的Class對(duì)象,并且這個(gè)對(duì)象是唯一的,后續(xù)要?jiǎng)?chuàng)建這個(gè)類(lèi)的任何實(shí)例,都會(huì)根據(jù)這個(gè)Class對(duì)象來(lái)創(chuàng)建。因此每當(dāng)加載一個(gè)class文件的時(shí)候,都會(huì)創(chuàng)建一個(gè)與之對(duì)應(yīng)的Class對(duì)象。
- 解析一個(gè)類(lèi)的各個(gè)部分,形成一個(gè)對(duì)象。
- String類(lèi)加載示意圖
- 外存中的類(lèi),加載到內(nèi)存中,會(huì)形成該對(duì)象的Class類(lèi),例如:String類(lèi),加載到內(nèi)存中,就是StringClass對(duì)象。
也就是說(shuō)類(lèi)是java.lang.Class類(lèi)的實(shí)例對(duì)象,而Class是所有類(lèi)的類(lèi)
對(duì)于普通的對(duì)象,一般都的創(chuàng)建方式:
String s = new String();
- 既然類(lèi)都是Class的對(duì)象,那么能否像普通對(duì)象一樣創(chuàng)建呢,當(dāng)看源碼時(shí),是這樣寫(xiě)的 :
private Class(ClassLoader loader) {
classLoader = loader;
}
- 源碼里構(gòu)造器是私有的,只有JVM可以創(chuàng)建Class的對(duì)象,雖然我們不能new一個(gè)Class對(duì)象,但是可以從已有的類(lèi)得到一個(gè)Class對(duì)象,共有三種方式,如下:
// 類(lèi)名.class 通過(guò)獲取類(lèi)的靜態(tài)成員變量class得到(任何類(lèi)都有一個(gè)隱含的靜態(tài)成員變量class)
Class<?> clazz = String.class;
// 對(duì)象.getClass
Class<?> clazz2 = new String().getClass();
// Class.forName("全量限定名")
Class<?> clazz3 = Class.forName("java.lang.String");
- (
注意:這三種方式都是利用反射獲取的都是同一個(gè)Class對(duì)象,這也叫做String的類(lèi)類(lèi)型,也就是描述何為類(lèi),一個(gè)類(lèi)都有哪些東西,所以可以通過(guò)類(lèi)類(lèi)型知道一個(gè)類(lèi)的屬性和方法,并可以調(diào)用一個(gè)類(lèi)的屬性和方法,這就是反射的基礎(chǔ)。)
反射
反射是指
在程序的運(yùn)行期間動(dòng)態(tài)的去操作某個(gè)Class對(duì)象里面的成員(包括類(lèi)信息、屬性信息、方法信息等元素)。它可以讓Java這種靜態(tài)語(yǔ)言具備一定的動(dòng)態(tài)性。目前大部分的開(kāi)源框架實(shí)現(xiàn)都是基于反射的機(jī)制實(shí)現(xiàn)。
JVM → 類(lèi)加載 → class文件 → 創(chuàng)建 → Class對(duì)象 → 構(gòu)建類(lèi)的實(shí)例 → instance(實(shí)例);
重點(diǎn)在運(yùn)行時(shí)動(dòng)態(tài)的操作Class對(duì)象。
反射機(jī)制的利與弊
為何要用反射機(jī)制?直接new對(duì)象不ok了嗎,這就涉及到了動(dòng)態(tài)與靜態(tài)的概念
- 靜態(tài)編譯:在編譯時(shí)確定類(lèi)型,綁定對(duì)象,即通過(guò)。
- 動(dòng)態(tài)編譯:運(yùn)行時(shí)確定類(lèi)型,綁定對(duì)象。動(dòng)態(tài)編譯最大限度發(fā)揮了java的靈活性,體現(xiàn)了多態(tài)的應(yīng)用,有利于降低類(lèi)之間的藕合。
- 優(yōu)點(diǎn):
- 可以實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象和編譯。比如,一個(gè)軟件,不可能第一個(gè)版本就把它設(shè)計(jì)的很完美,當(dāng)這個(gè)程序編譯成功,發(fā)布后,當(dāng)發(fā)現(xiàn)某些功能需要更新時(shí),我們不可能要用戶(hù)把舊版的卸載,再重新安裝新的版本。采用靜態(tài)的話(huà),需要把整個(gè)程序重新編譯一次才可以實(shí)現(xiàn)功能的更新,而采用反射機(jī)制的話(huà),它就可以不用卸載,只需要在運(yùn)行時(shí)才動(dòng)態(tài)的創(chuàng)建和編譯,就可以實(shí)現(xiàn)該功能。
一句話(huà)總結(jié):運(yùn)行期類(lèi)型的判斷,動(dòng)態(tài)類(lèi)加載,動(dòng)態(tài)代理就使用了反射
- 可以實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象和編譯。比如,一個(gè)軟件,不可能第一個(gè)版本就把它設(shè)計(jì)的很完美,當(dāng)這個(gè)程序編譯成功,發(fā)布后,當(dāng)發(fā)現(xiàn)某些功能需要更新時(shí),我們不可能要用戶(hù)把舊版的卸載,再重新安裝新的版本。采用靜態(tài)的話(huà),需要把整個(gè)程序重新編譯一次才可以實(shí)現(xiàn)功能的更新,而采用反射機(jī)制的話(huà),它就可以不用卸載,只需要在運(yùn)行時(shí)才動(dòng)態(tài)的創(chuàng)建和編譯,就可以實(shí)現(xiàn)該功能。
- 缺點(diǎn):
1.對(duì)性能有影響。反射相當(dāng)于一系列解釋操作,通知JVM要做的事情。性能比直接的java代碼執(zhí)行相同的操作要慢很多。
2.由于反射允許代碼執(zhí)行一些在正常情況下不被允許的操作(比如訪(fǎng)問(wèn)私有的屬性和方法),所以使用反射可能會(huì)導(dǎo)致意料之外的副作用,反射代碼破壞了抽象性,因此當(dāng)平臺(tái)發(fā)生改變的時(shí)候,代碼的行為就有可能也隨著變化。
反射機(jī)制的相關(guān)操作
創(chuàng)建實(shí)例
// 在反射操作之前的第一步,就是要先獲取Class對(duì)象
Class<?> clazz = Class.forName("org.demo.reflect.bean.People");
// 根據(jù)Class對(duì)象創(chuàng)建一個(gè)實(shí)例
clazz.newInstance();
動(dòng)態(tài)操作屬性
-
通過(guò)Class對(duì)象可以動(dòng)態(tài)的獲取和操作類(lèi)中的屬性,屬性在JDK中有一個(gè)類(lèi)來(lái)進(jìn)行封裝,就是Field,Field提供了一些常用的API方法讓我們?nèi)ピL(fǎng)問(wèn)和操作類(lèi)中的屬性
getField()// 獲取所有公開(kāi)的屬性字段(包括繼承父類(lèi)的公有屬性)
getDeclaredField()// 獲取本類(lèi)所有(包括公有和私有,但是不包括父類(lèi)的)的屬性字段(注意:如果要訪(fǎng)問(wèn)和操作私有屬性,必須調(diào)用setAccessible方法,打開(kāi)訪(fǎng)問(wèn)開(kāi)關(guān))
getFields()// 獲取所有公有的屬性(包括繼承自父類(lèi)的公有屬性)
getDeclaredFields()// 獲取本類(lèi)所有的屬性(包括共有和私有的,但是不包括父類(lèi)的)
set()// 給屬性賦值,需要傳入兩個(gè)參數(shù),第一個(gè)參數(shù)是當(dāng)前類(lèi)的一個(gè)實(shí)例,第二個(gè)參數(shù)是具體要賦予的值
get()// 獲取屬性的值,需要傳入一個(gè)當(dāng)前類(lèi)的實(shí)例作為參數(shù)
getName()// 獲取屬性的名稱(chēng)
getType()// 獲取屬性的類(lèi)型
isAnnotationPresent()// 判斷該屬性上是否定義了指定的注解,需要傳入一個(gè)注解的Class對(duì)象作為參數(shù)
getAnnotation()// 獲取當(dāng)前屬性上的注解對(duì)象,需要傳入一個(gè)注解的Class對(duì)象作為參數(shù)
public static void main(String[] args) throws Exception {
// 在反射操作之前的第一步,就是要先獲取Class對(duì)象
Class<?> clazz = Class.forName("org.demo.reflect.bean.People");
// 根據(jù)Class對(duì)象創(chuàng)建一個(gè)實(shí)例
Object instance = clazz.newInstance();
// 獲取指定的屬性
Field f1 = clazz.getField("userName");
// 獲取屬性的值,get方法需要傳入一個(gè)當(dāng)前類(lèi)的實(shí)例
Object value = f1.get(instance);
System.out.println(value);
// 通過(guò)反射給屬性賦值
// 第一個(gè)參數(shù)是當(dāng)前類(lèi)的實(shí)例,第二個(gè)參數(shù)是要賦予的值
f1.set(instance, "godql");
value = f1.get(instance);
System.out.println(value);
// 獲取一個(gè)私有的屬性
// 如果需要訪(fǎng)問(wèn)和操作私有的成員,必須打開(kāi)訪(fǎng)問(wèn)開(kāi)關(guān)
// 打開(kāi)訪(fǎng)問(wèn)開(kāi)關(guān)其實(shí)就是破壞封裝
Field f2 = clazz.getDeclaredField("age");
// 強(qiáng)制打開(kāi)訪(fǎng)問(wèn)權(quán)限
f2.setAccessible(true);
Object value2 = f2.get(instance);
System.out.println(value2);
f2.set(instance, 30);
value2 = f2.get(instance);
System.out.println(value2);
// 獲取屬性的名稱(chēng)
System.out.println(f1.getName());
System.out.println(f2.getName());
// 獲取屬性的類(lèi)型
System.out.println(f1.getType());
System.out.println(f2.getType());
// 獲取所有公有的屬性(包括繼承自父類(lèi)的公有屬性)
Field[] fs1 = clazz.getFields();
// 獲取本類(lèi)所有的屬性(包括共有和私有的,但是不包括父類(lèi)的)
Field[] fs2 = clazz.getDeclaredFields();
// 判斷當(dāng)前屬性上是否定義了注解
System.out.println(f1.isAnnotationPresent(MyAnno.class));
ystem.out.println(f2.isAnnotationPresent(MyAnno.class));
// 獲取屬性上定義的注解
MyAnno anno = f1.getAnnotation(MyAnno.class);
// 獲取注解上的屬性值
System.out.println(anno.name());
}
動(dòng)態(tài)操作方法
-
對(duì)于Class中的方法,API也提供了相應(yīng)的類(lèi)來(lái)進(jìn)行封裝,就是Method
getMethod()// 獲取指定的公共的方法(包括繼承自父類(lèi)公共的),需要傳遞兩個(gè)參數(shù),第一個(gè)參數(shù)是方法名稱(chēng),第二個(gè)參數(shù)是一個(gè)可變參數(shù),傳遞的是方法參數(shù)的類(lèi)型
getMethods()// 獲取所有的公共的方法(包括繼承父類(lèi)的公共方法)。
getDeclaredMethod()// 獲取本類(lèi)中指定的方法(包括私有和共有的,不包括父類(lèi)的),需要傳遞兩個(gè)參數(shù),第一個(gè)參數(shù)是方法名稱(chēng),第二個(gè)參數(shù)是一個(gè)可變參數(shù),傳遞的是方法參數(shù)的類(lèi)型。如果是私有方法,同樣需要先打開(kāi)訪(fǎng)問(wèn)開(kāi)關(guān)(setAccessible(true))。
getDeclaredMethods()// 獲取本地中所有的方法(包括私有和公共的,不包括父類(lèi))
getName()// 獲取方法名稱(chēng)
getReturnType()// 獲取方法的返回值類(lèi)型
getParameterTypes()// 獲取方法中所有的參數(shù)類(lèi)型
getParameterCount()// 獲取方法中參數(shù)的總個(gè)數(shù)
getParameters()// (JDK1.8新特性)獲取方法中所有的參數(shù)信息,每一個(gè)參數(shù)信息都是一個(gè)Parameter類(lèi)的對(duì)象??梢酝ㄟ^(guò)這個(gè)對(duì)象獲取各個(gè)參數(shù)的類(lèi)型以及名稱(chēng)(注意:如果要獲取參數(shù)名,在編譯的時(shí)候需要加上一個(gè)parameters參數(shù),如:javac -parameters Xxx.java?;蛘呤窃陂_(kāi)發(fā)環(huán)境中設(shè)置相應(yīng)的編譯選項(xiàng))。
invoke()// 回調(diào)當(dāng)前方法,需要傳遞兩個(gè)參數(shù),第一個(gè)是當(dāng)前類(lèi)的實(shí)例,第二個(gè)是一個(gè)可變參數(shù),需要傳入調(diào)用方法是所需的參數(shù)值。
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("org.demo.reflect.bean.People");
Object instance = clazz.newInstance();
// 獲取指定的Method
Method m1 = clazz.getMethod("say", String.class, int.class);
// 獲取方法名
System.out.println(m1.getName());
// 獲取方法的返回值類(lèi)型
System.out.println(m1.getReturnType());
// 獲取方法的所有參數(shù)類(lèi)型
Class<?>[] paramsType = m1.getParameterTypes();
for (Class<?> c : paramsType) {
System.out.println(c);
}
// 獲取參數(shù)名稱(chēng)(JDK1.8開(kāi)始支持)
Parameter[] params = m1.getParameters();
for (Parameter p : params) {
System.out.println("參數(shù)類(lèi)型:"+p.getType());
System.out.println("參數(shù)名稱(chēng):"+p.getName());
}
// 通過(guò)當(dāng)前的方法,獲取定義這個(gè)方法的類(lèi)
Class<?> c = m1.getDeclaringClass();
System.out.println(c.getName());
// 方法回調(diào),目的就是通過(guò)反射去調(diào)用一個(gè)方法
m1.invoke(instance, "godql", 21);
}
動(dòng)態(tài)操作構(gòu)造方法
-
Constructor是在反射API中用于封裝構(gòu)造方法的一個(gè)類(lèi),因此通過(guò)這個(gè)類(lèi)可以獲取構(gòu)造方法的一些信息,以及通過(guò)這個(gè)對(duì)象來(lái)實(shí)例化一個(gè)類(lèi)的實(shí)例。
getConstructor()// 獲取無(wú)參并且公共的構(gòu)造方法
getDeclaredConstructor()// 獲取一個(gè)構(gòu)造方法可以是私有的也可以是公共的,需要傳入一個(gè)可變參數(shù),就是構(gòu)造方法的參數(shù)類(lèi)型(注意:如果是私有的,必須先打開(kāi)訪(fǎng)問(wèn)開(kāi)關(guān))
newInstance()// 通過(guò)構(gòu)造方法創(chuàng)建實(shí)例,也需要傳入一個(gè)可變參數(shù),傳入的是具體的值
getConstructors()// 獲取所有公共的構(gòu)造方法,返回的是一個(gè)Constructor數(shù)組
getDeclaredConstructors()// 獲取所有的構(gòu)造方法(包括私有和共有的),同樣返回的是一個(gè)數(shù)組
getParameters()// 獲取所有的參數(shù)對(duì)象,和Method一樣
getParameterTypes()// 獲取所有的參數(shù)類(lèi)型,同Method一樣
public static void main(String[] args) throws Exception {
Class<?> clazz = People.class;
// 獲取無(wú)參的構(gòu)造方法
Constructor<?> c1 = clazz.getConstructor();
// 獲取構(gòu)造方法的名稱(chēng)
System.out.println(c1.getName());
// 獲取一個(gè)私有并且?guī)?shù)的構(gòu)造方法
Constructor<?> c2 = clazz.getDeclaredConstructor(String.class);
// 可以通過(guò)構(gòu)造方法實(shí)例化一個(gè)對(duì)象
//(注意:如果默認(rèn)有一個(gè)無(wú)參并且是公共的構(gòu)造方法,
// 那么可以直接使用class.newInstance()方法創(chuàng)建實(shí)例,
// 如果構(gòu)造方法是私有的,或者是帶參數(shù)的,就必須先獲取
// Constructor對(duì)象,在通過(guò)這個(gè)對(duì)象來(lái)創(chuàng)建類(lèi)實(shí)例)
// 1.適用于無(wú)參并且是公共的構(gòu)造方法
/*
Object instance = clazz.newInstance();
System.out.println(instance);
*/
// 2.適用于帶參數(shù)或是私有的構(gòu)造方法
// 由于構(gòu)造方法也可以私有化,所以必須先打開(kāi)訪(fǎng)問(wèn)開(kāi)關(guān)
c2.setAccessible(true);
Object instance = c2.newInstance("godql");
System.out.println(instance);
// 獲取所有public修飾的構(gòu)造方法
Constructor<?>[] cons = clazz.getConstructors();
// 獲取所有構(gòu)造方法(包括私有的)
Constructor<?>[] cons2 = clazz.getDeclaredConstructors();
}
Class中的一些API
-
Class對(duì)象本身提供了很多的API方法用于獲取和操作Class對(duì)象。
getPackage()// 獲取當(dāng)前類(lèi)所在的包,使用Package對(duì)象進(jìn)行封裝,可以從中獲取包的信息,例如:包名
getSimpleName()// 獲取當(dāng)前類(lèi)的簡(jiǎn)單類(lèi)名(不包括包名)
getName()// 獲取當(dāng)前類(lèi)的完整類(lèi)名(包括包名)
getSuperclass()// 獲取當(dāng)前類(lèi)的父類(lèi),返回的也是一個(gè)Class對(duì)象
getInterfaces()// 獲取當(dāng)前類(lèi)所實(shí)現(xiàn)的所有接口,返回的是一個(gè)Class數(shù)組
isAnnotationPresent()// 判斷當(dāng)前類(lèi)上是否定義了注解
getAnnotation()// 獲取類(lèi)上定義的注解
通過(guò)反射了解集合泛型的本質(zhì)
- Java中集合的泛型,是防止錯(cuò)誤輸入的,只在編譯階段有效,繞過(guò)編譯到了運(yùn)行期就無(wú)效了。
public static void main(String[] args) {
List list = new ArrayList();
List<String> list1 = new ArrayList<>();
list.add("godql");
// list1.add(20); 錯(cuò)誤的
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2); // 結(jié)果:true,說(shuō)明類(lèi)類(lèi)型完全相同
// 反射的操作都是編譯之后的操作(運(yùn)行時(shí))
/*
* 以上說(shuō)明編譯之后集合的泛型是泛型擦除的
* Java中集合的泛型,是防止錯(cuò)誤輸入的,只在編譯階段有效,繞過(guò)編譯就無(wú)效了。
* 驗(yàn)證: 通過(guò)方法的反射來(lái)操作,繞過(guò)編譯
*/
try {
// 通過(guò)動(dòng)態(tài)操作方法的反射得到add方法
Method m = c2.getMethod("add", Object.class);
// 方法回調(diào) 給list1添加一個(gè)int型的,這是在運(yùn)行時(shí)的操作,所以編譯器編譯時(shí)沒(méi)有泛型檢查,所以不會(huì)報(bào)錯(cuò)
// 繞過(guò)編譯操作
m.invoke(list1, 20);
// 驗(yàn)證是否有添加進(jìn)list集合里
System.out.println(list1.size());
// 這時(shí)候不能使用foreach遍歷,否則集合會(huì)認(rèn)為集合里邊全是String類(lèi)型的值
// 且有類(lèi)型轉(zhuǎn)換錯(cuò)誤,因?yàn)檫@個(gè)集合里面有int類(lèi)型、String類(lèi)
System.out.println(list1);
} catch (Exception e) {
e.printStackTrace();
}
}
使用場(chǎng)景
- 通用的應(yīng)用程序
- 框架比如:
1.Spring 的 Ioc/Di
2.SpringMVC 監(jiān)控 Controller中的注解
3.MyBatis 利用反射獲取和設(shè)置對(duì)象值
4.Struts2 的 FormBean 和頁(yè)面之間...
5.Hibernate的 find(Class clazz)
6.JavaBean和JSP之間調(diào)用
7.JDBC 的 classForName()
源碼下載
I was within and without.
原文:http://www.godql.com/blog/2017/07/07/Core-Java/
作者:Dr.Lester










