「JAVA」萬字長篇詳述字節碼對象與反射機制完成動態編程

2020-12-06 老夫編程說
Java 反射

在Java的開發環境中,運行java文件需要使用:java xx.java 命令,運行java命令後,便會啟動JVM,將字節碼文件加載到JVM中,然後開始運行;當運行java命令時,該命令將會啟動一個JVM進程,在這個JVM進程中,會保存有該JVM創建的所有線程、變量、對象,這些線程、變量、對象會共享該JVM的內存區域。

當出現以下情況時,JVM進程會退出:

程序正常執行結束,JVM正常退出;使用System.exit(0)方法,JVM正常退出;程序運行出現異常,並且沒有對異常進行處理(捕獲、或者拋出)時;作業系統強制結束JVM進程,比如:關機、通過任務管理器強制結束等;JVM進程一旦退出,該進程中內存區域保存的數據(線程、變量、對象等數據)將會丟失,因為其都是保存在內存中的,並沒有寫入到硬碟。

01類加載機制

在Java 類的整個「漫長的」生命周期中,類的加載僅僅只是個開始,因為類在加載後還要經歷一系列的處理,才能被JVM接受,併到處運行。

根據JVM規範,Java 程序的整個生命周期會經歷5個階段:加載 -> 連結(驗證、準備、解析) -> 初始化 -> 使用 -> 卸載;因為在連結階段會有驗證、準備、解析三個步驟,所以也可以說Java 程序的生命周期會經歷7個階段(使用驗證、準備、解析三個步驟來替代連結階段)。

Java 程序的生命周期

當JVM要使用某個類某,而該類還未被加載進JVM內存中時,JVM會通過加載,連結,初始化三個步驟來對該類進行初始化操作,以便於後期運行。

1.類的加載

類的加載是指將類的class文件(字節碼文件)載入JVM內存中,並為之創建一個java.lang.Class對象,也就是字節碼對象

類的加載過程由類加載器(ClassLoader)完成,類加載器由JVM提供,我們稱之為系統類加載器,同時,我們也可以繼承ClassLoader類來提供自定義類加載器。詳情可查看:「JAVA」萬字長篇詳述字節碼對象與反射機制完成動態編程

類的加載過程中也會對字節碼文件進行驗證,不過這裡的驗證並不會深入,而是把重點放在字節碼文件的格式上;類加載器不僅可以加載本地字節碼文件,加載jar包中的字節碼,更是可以通過網絡加載字節碼。

2.類的連結

當類的字節碼文件被加載進JVM內存之後,JVM便會創建一個對應的Class對象(也可以叫字節碼對象),把字節碼指令中對常量池中的索引引用轉換為直接引用,接著把類的字節碼指令合併到JRE中。連結包含三個步驟:

驗證:檢測被加載的類是否有正確的內部結構。準備:負責為類的static變量分配內存,並根據變量的數據類型設置默認值。解析:把字節碼指令中對常量池中的索引引用轉換為直接引用。

3.類的初始化

在完成類的連結之後,JVM便會對類進行初始化,這裡的初始化主要是對static變量進行初始化,並非是類的實例化,因為類的實例化屬於「使用」階段。

類的初始化一個類包含以下幾個步驟:

如果該類還未被加載和連結,則程序先加載並連結該類。如果該類的直接父類還未被初始化,則先初始化其父類,若是父類的父類還未初始化,就會先初始化父類的父類,依次類推,直至沒有父類為止。如果類中有初始化語句(靜態代碼塊),則JVM會依次執行這些初始化語句。

02運行時信息

運行時信息,即Runtime Type Information,是指程序在運行時的信息。在Java 中有兩種方式可以得到運行時信息:

一是通過RTTI,即Run-Time Type Identification,這種方式假設我們在程序編寫時就已經知道了所有對象的類型,主要是通過字節碼對象Class來獲取;二是通過「反射」機制,反射提供一組api,通過調用api,便能獲取運行時的類信息;Java 中通過java.lang.reflect類庫來支持反射;

03Class 對象

Class 類

Class類:我們平時在開發中定義的類是用來描述業務邏輯的;比如Teacher.java,Student.java等,而Class類用來描述我們所定義的業務邏輯的類,也就是描述類的類。

Class類的實例:其實就是JVM中的字節碼對象,一個字節碼文件有一個字節碼對象,一個Class實例表示在JVM中的某個類或者接口,當然,也包括枚舉和註解,因為枚舉是一種特殊的類,註解是一種特殊的接口。

每一個類都有都有一個Class對象,也就是說每個類都有一個字節碼對象,有一個字節碼文件。當第一次使用類的時候,該類的字節碼文件會被加載到JVM中,創建一個字節碼對象;此時,該字節碼對象就是一個Class實例。

既然每一個類都有一個Class對象,那麼這些Class對象之間是如何區分它所表示的是哪一個類的字節碼的呢?為了解決這個問題,Java 為Class提供了泛型:Class<T>。

java.lang.String類的字節碼類型:Class<java.lang.String>;java.util.Date類的字節碼類型:Class<java.util.Date>;java.util.ArrayList類的字節碼類型:Class<java.util.ArrayList>;

創建Class對象

創建字節碼對象,有3種方式:(使用java.util.Date類做演示)

1.使用類字面量,即使用類的class屬性;

Class<java.util.Date> clazz1 = java.util.Date.class;2.使用對象的getClass();方法;

java.util.Date date = new java.util.Date();Class<?> clazz2 = date.getClass();3.使用Class.forName(String className);方法;

Class<?> clazz3 = Class.forName("java.util.Date");在上述的代碼案例中使用了通配符「?」,通配符「?」表示「任何類」,通配符的使用使得Class對象的類型更加寬泛。

因為同一個類在JVM中只存在一個字節碼對象,因此在上述的代碼中存在:

clazz1 == cclazz2 == clazz3;

基本數據類型的字節碼對象

在上述講了三種獲取Class對象的方式,都是類或者對象獲取Class對象;但是基本數據類型不能表示為對象,不能使用getClass的方式;基本數據類型沒有類名,所以也不能使用Class.forName的方式,那麼該如何表示基本類型的字節碼對象呢?

Java 中,所有的數據類型都有class屬性,包括基本數據類型;如何獲取呢?使用字面量的方式,這種方式不僅可以應用於類,還可以應用於接口、數組、和基本數據類型。語法如下:

Class clazz = 數據類型.class;詳情如下:

基本數據類型的Class對象

預加載

在JVM啟動期間,會預先加載一部分核心類庫的字節碼文件到在JVM中,其中就包括了九大基本數據類型。並且在8大基本數據類型的包裝類中,都有一個常量:TYPE,用於返回該包裝類對應基本類的字節碼對象;

class &amp; TYPE

那麼如下的代碼案例便解釋得通了,因為Integer和int是不同的數據類型,所以會有不同輸出結果:

Integer和int是不同的數據類型

數組的字節碼對象

數組其實是對象,所以數的Class實例便可以使用引用數據類型的方式獲取:

方式1:數組類型.class;

數組類型.class

方式2:數組對象.getClass();

數組對象.getClass();

注意:所有的具有相同的維數和相同元素類型的數組共享同一個字節碼對象,和元素沒有關係.

共享同一個字節碼對象

Class類和Object類

Object:描述所有的對象,其擁有所有對象的共同的方法。Class:描述所有的類型,其擁有所有類型的相同的方法;需要注意:Class類也是Object的子類每個類都有其字節碼對象,通過字節碼對象能獲取到類的很多信息,在Class類中提供了很多的api來獲取類的運行時信息,以下是一些常用的:

常用的字節碼對象api

但是,上述的通過字節碼對象獲取類的信息是建立在我們提前知道確切的數據類型的基礎之上的,如果我們事先不知道數據類型,那麼便不能通過獲取類的字節碼對象來獲取類的運行時信息;在這樣的情況下,又該如何獲取對象的運行時信息,該如何動態的創建類的對象?

對於這個問題,java.lang.reflect類庫中提供了一個叫做「反射」的組件,可以配合Class類來共同提供動態信息獲取,下面,就來一起看看吧!

04反射

何為「反射」

眾所知周,對象有編譯類型和運行類型。現有如下的代碼案例:

編譯類型和運行類型

需求:通過obj對象,調用java.util.Date類中的toLocaleString方法:obj.toLocaleString(); ,此時編譯報錯,如何解決編譯錯誤?

案例分析:編譯時,會檢查該編譯類型中是否存在toLocaleString方法;如果存在,編譯成功,否則編譯失敗;此時編譯失敗是因為在Object類中沒有toLocaleString方法,因此要將obj轉換為java.util.Date類型,才能正確調用toLocaleString方法。

解決方案:因為obj的真實類型是java.util.Date類,所以我們可以把obj對象強制轉換為java.util.Date類型:

解決方案

上述解決方案是在知道obj真實類型的情況下解決的,那麼如果不知道obj的真實類型,就不能進行強轉,也不能使用Class對象;此時,問題又該如何解決?

遇到此等問題,就不得不祭出法寶——反射了,在java.lang.reflect類庫中提供了一組api用於提供對反射的支持。

反射:獲取類的元數據(元數據:描述類的類的數據的過程,在運行期間,動態的獲取類中的成員信息,包括構造器、方法、欄位、內部類、父類、接口等;並且把類中的每一種成員,都分別描述成一個新的類:

Class:表示所有的類;Constructor:表示所有的構造器;Method:表示所有的方法;Field:表示所有的欄位;

在Java 中,Class類和java.lang.reflect類庫共同提供了對「反射」的支持,在java.lang.reflect類庫中就包含了Constructor、Method、Field等類,用於描述類的元數據,在字節碼對象中提供了獲取這些元數據的api:

獲取這些元數據的api

05反射之構造器

獲取構造器

在Class類中提供了獲取類的構造器的方法:

獲取類的構造器的方法

在上述方法中:

Constructor類表示類中構造器的類型,Constructor的實例就是某個類中的某一個構造器;參數 parameterTypes表示:構造器參數的Class類型,方法可藉由傳入的參數類型來獲取對應的構造器;Constructor類提供了用於通過反射獲取到的構造器的來創建實例的方法:newInstance();

newInstance()

由於上述方法時在Class類中,所有在調用這些方法前需要先獲取類的字節碼對象,再通過字節碼對象來調用獲取構造器的方法。

先有Student類,在Student類中有三個構造器,分別是:無參構造器,公共帶參構造器,私有構造器;代碼如下:

Student類

無參構造器

無參構造器

公共構造器

公共構造器

私有構造器:在使用私有構造器創建實例時,由於私有構造器不能被外界訪問,所以在創建實例前先設置其可以訪問,案例代碼如下:

私有構造器

如果一個類中的構造器是外界可以直接訪問(非private 的),同時沒有參數,那麼可以直接使用Class類中的newInstance方法創建對象示例,其效果等同於使用new關鍵創建對象。

06反射之方法

獲取方法

在Class類中提供了獲取類的方法的api,由於方法也是在Class類中,所以在獲取方法之前,也是需要先獲取其所在類的字節碼對象,再來獲取方法,最後再來執行方法。

Class類中獲取類的方法的api:

Class類中獲取類的方法的api

上述這些api的返回值均為Method類,Method類是用於描述反射中的方法的類,在Method類中,提供了執行反射獲取的方法的方法invoke():

執行反射獲取的方法的方法invoke()

下面,通過一個案例來實踐上述方法的使用:

同樣,會有Student類,類中有public方法,static方法、private方法,分別用於演示通過反射獲取類中public方法,static方法,private方法;

Student類:

獲取所有方法

獲取所有方法

調用public方法

調用public方法

調用static方法,使用反射調用靜態方法時,由於靜態方法不屬於任何對象,它只屬於類本身。所以在執行靜態方法時,傳入invoke方法的第一個參數就不能是任何對象,在這裡需要將其設置為null。

調用static方法

調用private方法,在調用私有方法之前,要設置該方法為可訪問的,因為Method是AccessibleObject子類,所以在Method對象中可以通過調用setAccessible(true);來設置訪問可以訪問。

調用private方法

可變參數,方法的可變參數在底層是其實是作為數組處理的,所以在執行可變參數的方法時,可直接傳入數組參數,但傳參數,須得區分引用數據類型和基本數據類型;

可變參數

在本文中,介紹了類的加載過程,詳細描述的了JVM對字節碼文件的處理過程,從中也看到了不少JVM底層的處理細節。

字節碼對象的創建使得RTTI得以獲取大量被底層屏蔽的信息,通過這些信息能讓我們更加了解Java 的設計思想。

通過反射提供的強大功能,不僅可以訪問到類的運行時信息,還可以訪問到類中不允許被外界訪問的private屬性和方法,這無異於又打開了另一個新世界的大門,我們能通過反射編寫動態代碼,使得編程更加靈活。這也是Java 語言的一大特色,能夠很好的與C、C++這樣的語言區分開來。

到這裡就結束了,能完整看完的小夥伴,已實屬不易,給你們點讚!由於作者知識有限,若有錯誤之處,還請多多指出!

完結。老夫雖不正經,但老夫一身的才華

相關焦點

  • Java反射機制深入詳解
    這些工具通過 reflection 動態的載入並取得 Java 組件(類) 的屬性。反射是從1.2就有的,後面的三大框架都會用到反射機制,涉及到類」Class」,無法直接new CLass(),其對象是內存裡的一份字節碼.Class 類的實例表示正在運行的 Java 應用程式中的類和接口。枚舉是一種類,注釋是一種接口。
  • 簡析:Java反射、Java反射定義、反射的基石
    Java反射定義在程序運行過程中,對於任意一個類,可以獲得該類的屬性和方法;對於任意一個對象,可以調用該對象的任意一個屬性和方法。在運行時動態獲取類的信息和動態調用對象的屬性和方法稱為Java反射機制。
  • Java 反射:框架設計的靈魂
    比如 C 語言;Java 嚴格來說也是編譯型語言,但又介於編譯型和解釋型之間;Java 不直接生成機器碼而是生成中間碼:編譯期間,是將源碼交給編譯器生成 class 文件(字節碼),這個過程中只做了翻譯的工作,並沒有把代碼放入內存運行;當進入運行期,字節碼才被 Java 虛擬機加載、解釋成機器語言並運行。
  • 萬字概覽 Java 虛擬機
    JVM 的作用是什麼如圖所示,我們編寫的 Java 代碼通過 Java 編譯器編譯後成為字節碼,即我們常說的 .class 文件。這些字節碼文件和 JDK 類庫的字節碼文件分別通過 JVM 提供的基本的三個類加載器被加載到 JVM 之中,JVM 就是負責解析、執行這些字節碼並管理和協調整個執行生命周期各項事件的平臺。
  • Java編程中基礎反射詳細解析
    反射機制允許程序在運行時取得任何一個已知名稱的class的內部信息,包括包括其modifiers(修飾符),fields(屬性),methods(方法)等,並可於運行時改變fields內容或調用methods。那麼我們便可以更靈活的編寫代碼,代碼可以在運行時裝配,無需在組件之間進行原始碼連結,降低代碼的耦合度;還有動態代理的實現等等。
  • 什麼是JAVA反射機制,詳細解讀JAVA面試的核心技術
    一、什麼叫Java反射機制?Java中的反射機制是指在運行狀態中,對於任意一個類,能夠動態獲取這個類中的屬性和方法;對於任意一個對象,都能夠任意調用它的屬性和方法。這種動態獲取類的信息以及動態調用對象方法的功能稱為Java的反射機制。
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    java源文件編譯為class字節碼 類加載器把字節碼加載到虛擬機的方法區。因此,需要把class字節碼文件加載到Java虛擬機來。 虛擬機把描述類的數據從 Class 文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的 Java 類型,這就是虛擬機的類加載機制。
  • 萬字梳理,帶你拿下 Java 面試題!
    安全性, 編譯後會將所有的代碼轉換為字節碼,人類無法讀取。它使開發無病毒,無篡改的系統/應用成為可能。動態性,它具有適應不斷變化的環境的能力,它能夠支持動態內存分配,從而減少了內存浪費,提高了應用程式的性能。分布式,Java 提供的功能有助於創建分布式應用。
  • Java面試高頻考點:反射機制使用大全
    作為一個Java開發工程師,在面試的過程中,反射機制也是經常會被問到的一個問題。例如Spring的IOC實現機制,其底層都是依賴於java的反射機制,因此,這是一個非常重要的知識點。對於初學java的同學來說,掌握其使用方法很有必要。
  • Java基礎教程:java反射機制教程
    Java反射說的是在運行狀態中,對於任何一個類,我們都能夠知道這個類有哪些方法和屬性。很多動力節點的學員在面試中都會被問到Java反射機制這個問題,為了幫助大家更好的掌握這個知識點,小編整理了一些資料分享給大家。
  • 世界排行第一的程式語言:java迎來25歲生日
    作為全球排名第一的程式語言,本周末Java將迎來 25 歲生日。Java起源於 1991 年的「 Oak」項目,由James Gosling領導。面向對象的Java以其「一次編寫,隨處運行」的可移植性而聞名,因為Java虛擬機支持多種硬體平臺和作業系統以及Java applet可以從網頁上運行。
  • 學Java反射,看這篇就夠了 | 原力計劃
    利用Java反射的機制,就可以讓第一個程式設計師在沒有得到第二個程式設計師所寫的類的時候,來完成自身代碼的編譯。解釋四:如果你是方法,快遞員是虛擬機。快遞員通過地址查地圖找你的叫反射調用。直接去找你的叫直接調用。
  • Java反射初探 ——「當類也學會照鏡子」
    動態加載類 我理解的「反射」的意義(僅個人理解哈) 我理解的java反射機制就是: 提供一套完善而強大的API「反射「類的結構。打個比方,反射機制就像是一面鏡子,而類就像是一個在照著鏡子的人。鏡子(反射機制)照出(反射)了人的全貌(類的全方位的信息,例如方法,成員變量和構造器等的相關信息)為什麼要照鏡子?
  • 「Java面試題」反射
    動態語言動態語言,是指程序在運行時可以改變其結構:新的函數可以引進,已有的函數可以被刪除等結構上的變化。比如常見的 JavaScript 就是動態語言,除此之外 Ruby,Python 等也屬於動態語言,而 C、C++則不屬於動態語言。
  • Java反射是什麼?看這篇絕對會了!
    例如java.util.HashMap / java.util.LinkedHashMap····如果要創建其它類例如WeakHashMap,我也不需要修改上面這段源碼。我們來回顧一下如何從 new 一個對象引出使用反射的。
  • 2020年Java基礎高頻面試題匯總
    java中提供了以下四種創建對象的方式:(1)new創建新對象(2)通過反射機制(3)採用clone機制(4)通過序列化機制17、有沒有可能兩個不相等的對象有相同的hashcodejava堆用於存儲對象實例,我們只要不斷的創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,就會在對象數量達到最大堆容量限制後產生內存溢出異常。
  • Java編程領域你需要懂得技術名詞解釋與常用開源框架理解
    IoC(DI)依賴注入實現的技術:反射機制、工廠模式。其中pring的BeanFactory主要實現步驟如下,解析配置文件(bean.xml)使用反射機制動態加載每個class節點中配置的類為每個class節點中配置的類實例化一個對象使用反射機制調用各個對象的seter方法,將配置文件中的屬性值設置進對應的對象將這些對象放在一個存儲空間(beanMap)中使用getBean
  • 如何使用JAVA反射/JAVA反射實例
    JAVA反射技術,在平時我們的開發中雖然很少會用到例如讀取配製文件可能就用到這個技術,但在我們所使用的框架源碼中是經常會用到的。getName())  ; // 得到類的名稱  System.out.println("類名稱:" + c3.getName())  ; // 得到類的名稱 }};2、反射如何操作對象
  • 反射——Java高級開發必須懂得
    描述:創建一個工具類名稱為ClassUtil,並且有一個靜態方法,參數為Object類型參數,首先獲取該對象的類類型,這裡使用第二種獲取方式,傳遞的是哪個對象,Class對象就是該對象類類型(這個功能是由native聲明的一個方法實現的,java中jni就是做本地方法的,該方法是由java來聲明,用C語言來實現),萬事萬物皆對象,方法同樣是對象
  • 適合Java新手的開源項目集合——在 GitHub 學編程
    先 clone 把源碼下載後,可以通過 java -jar FlappyBird.jar 直接運行,也可以通過運行源碼中的 GameApp:main 方法來啟動整個遊戲。1.2 Java 聖經高爾基說過:「書是人類進步的階梯」。