深入Java核心

類(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)生資源共享的情況。)
      • 遞歸棧
    • 本地方法棧

      • 本地方法棧的機(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ū)中的一部分,它存放的內(nèi)容是class文件中最重要的資源,JVM為每一個(gè)class對(duì)象都維護(hù)一個(gè)常量池。它主要存儲(chǔ)兩種類(lèi)型的常量。
        1. 字面常量
          • 字面常量通常就是在Java中定義的字面量值,如:int i =1,這個(gè)1就是字面量;String s = ("abc"),這個(gè)abc就是字面量?;蛘呤褂胒inal修飾的常量值等等。
        2. 符號(hào)引用
          • 符號(hào)引用主要包括類(lèi)和接口的完整類(lèi)名、屬性字段的名稱(chēng)和描述符、方法名稱(chēng)和描述符等信息
      • 常量池
  • 在Java當(dāng)中,8個(gè)基本數(shù)據(jù)類(lèi)型都有對(duì)應(yīng)的包裝類(lèi)型,而大部分包裝類(lèi)型都實(shí)現(xiàn)了常量池的技術(shù),除了DoubleFloat類(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)代理就使用了反射
  • 缺點(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()

源碼下載

GitHub

I was within and without.

原文:http://www.godql.com/blog/2017/07/07/Core-Java/
作者:Dr.Lester

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,642評(píng)論 18 399
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評(píng)論 19 139
  • (一)Java部分 1、列舉出JAVA中6個(gè)比較常用的包【天威誠(chéng)信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,250評(píng)論 0 62
  • 一:java概述:1,JDK:Java Development Kit,java的開(kāi)發(fā)和運(yùn)行環(huán)境,java的開(kāi)發(fā)工...
    ZaneInTheSun閱讀 2,806評(píng)論 0 11
  • redis cluster管理工具redis-trib.rb詳解 redis-trib.rb是redis官方推出的...
    很少更新了閱讀 993評(píng)論 0 1

友情鏈接更多精彩內(nèi)容