Java反射機制
了解Java的反射機制,首先要從RTTI開始。
文章的一開頭就開始裝逼了,RTTI是個什么東西。RTTI英文全稱:Run-Time Type Information,望文生義,RTTI是運行時類型信息。
什么是RTTI,有什么作用,編程時會用到嗎?這個問題先放在這里,等會再解答。
1, .class文件
學過一點點Java的人,都知道Java文件在編譯之后變成了class文件。問題又來了,class文件是什么?肯定有人回答:Class文件就是一個編譯后的文件,就像C/C++的.c和.cpp文件編譯之后編程了.o,還需要解釋嗎?要,必須要。
先來一張class文件的文件格式示意圖。
沒看懂?必然看不懂,先簡單介紹一下Class文件。
Class文件從文件類型上是二進制文件,問題又來了,什么是二進制文件?二進制文件就是機器能讀懂的語言,自從馮諾依曼的原型機出世,所有的計算機作為它的后代就只認識1和0這兩個數字。二進制文件就是一堆0和1。Class文件就是由一堆0和1堆起來的。但是,無規矩不成方圓,這些0和1是按照某種嚴格的規則放置在對應的位置上。JVM作為Java的運行環境,是規則的制定者和解讀者(盡管是Java的開發者們制定的,我們可以簡單地認為JVM做了這些事情,至少是開發者們讓JVM這么干的)。JVM按照規則來解讀0和1序列,然后告訴計算機如何去執行我們的程序所表達的意願。
但是,Class文件的結構不像XML那些描述型語言那樣松散和自由,它的定義是非常嚴格的,條件也非常的苛刻。Class文件沒有任何的分割符號,哪怕是一個空格一個回車(關於回車為什么叫做回車,點擊這里)。
在圖中Class文件的幾個部分,Header,Constant pool...等,這些數據項無論是順序還是數量都是被嚴格限定的,哪個字節代表什么含義,長度是多少,先后順序如何都被非常苛刻地限制,不允許改變。然后,你會看到Class attributes在class文件的最后,應該用過Java反編譯器吧,有沒有注意過Java反編譯器反編譯的class文件方法在上面,屬性都在下面,就是這個原因。
這篇博客講解的很詳細,http://blog.csdn.net/dc_726/article/details/7944154。
文件的開頭是Header,,這段十六進制碼表明了JDK的版本號,所以你把JDK1.7的class文件放在JDK1.6的環境下就不能運行,向下兼容。
2, Class類
扯得有點遠了。回到正題。
Java被編譯后,生成了.class文件,JVM此時就要去解讀.class文件。當程序主動去使用某個類時,如果這個類還沒有被加載到內存中,JVM會通過三個步驟對類進行初始化:
1.加載:由類加載器執行,該步驟查找字節碼,並從這些字節碼中創建一個Class對象
2.鏈接:在鏈接階段將驗證類中的字節碼,為靜態域分配存儲空間,並且如果必須的話,將解析這個類創建的對其他類的所有引用。
3.初始化:如果該類有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。
如以上三步,一個.class文件被融入到程序中供其調用。第一個步驟中,查找字節碼並生成一個Class對象,在Java中,如Thinking in Java中提到的,一切皆是對象。被編譯后的Java文件.class也被JVM解析為一個對象,這個對象就是java.lang.Class。
這樣當程序在運行時,每個java文件就最終變成了Class類對象的一個實例。我們通過Java的反射機制應用到這個實例,就可以去獲得甚至去添加改變這個類的屬性和動作,使得這個類成為一個動態的類(所謂的動態是按照動態語言的定義——在程序運行時改變其結構:心得函數可以被引進,已有的函數可以被刪除等在結構上的變化)。 由此,可以看出反射機制使得Java語言多么靈活。
3,Class類與反射機制
Class類的概念盡管很抽象,但是無疑,它是反射機制的起源,是Java語言中一個精巧美妙地設計。
介紹完.class文件是怎么回事,又簡單說明了.class和Class之間的關系,然后要看一下Class類的官方描述。
官方文檔是最權威的,先來看一下Class類中的API是如何描述Class類的。下面是翻譯后的中文文檔的描述:
Class類的實例表示正在運行的Java應用程序的類和接口。枚舉是一種類,注釋(注解)是一種接口。每個數組屬於被映射為Class對象的一個類,所有具有相同元素類型和維數的數組都共享該Class對象。基本的Java類型(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也表示為 Class 對象。Class沒有公用構造方法。Class對象是在加載類時由JVM以及通過調用類加載器中的defineClass方法自動構造的。
看完這個描述,並結合上面提到的三個步驟,應該會有更好的理解。
4,反射機制的定義和應用
在深入到反射機制之前,先探析一下反射機制的定義和應用。反射機制定義:Java反射機制是在運行狀態時,對於任意一個類,都能夠直到這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性。
我們不禁會想,得到這些屬性和方法並且去改變它們有什么用?覺得反射機制仿佛很遙遠,但是作為一個全職程序員,除了假期和雙休日,你幾乎每天都在和反射機制打交道,無論你使用的Eclipse系列的IDE(集成開發環境),還是使用的目前較為流行的IntelliJ IDEA,又或者是NetBeans,在你輸入一個對象的名稱,按下”.”時,總會彈出這個對象所擁有的所有方法列表供選擇。這個功能就是反射機制實現的,反射機制得到了這個對象的方法和屬性。
很顯然,是先有的反射機制,才有的自動智能提示,所以上面的例子只能勉強算作反射機制出現和應用的一個動機。
在反射機制設計之初,睿智的設計者們或許就有了分布式的野心。編程人員希望Java能夠提供在跨網絡的遠程平台上創建和運行對象的能力,這種能力的名字大家應該很熟悉,叫做遠程方法調用RMI,這便是編程人員想要在運行時獲取類的信息的又一個動機。RMI允許一個Java程序將對象分布到多台機器上,即在遠程就可以調用某個對象,就像你通過瀏覽器去使用別人提供的網頁服務一樣。這就促成了現在的分布式系統的崛起。然而在JDK的1.1版本中,Java反射機制就出現了。
5,Java反射機制的類庫支持及簡介
Class類和java.lang.reflect類庫一起構成了對Java反射機制的支持。其中最常使用到的類是Constructor,Field,Method,而這三個類都繼承了一個接口java.lang.reflect.Member。下面列舉介紹一下java.lang.reflect類庫中的類:
- AccessibleObject:Field,Method,和Constructor對象的基類。提供了將反射的對象標記為在使用時取消默認Java語言訪問控制檢查的能力。
- Array:提供了動態創建和訪問Java數組的方法。
- Constructor:提供關於類的單個構造方法的信息以及對它的訪問權限。
- Field: 提供有關類或接口的單個字段的信息,以及對它的動態訪問權限。反射的字段可能是一個類(靜態)字段或實例字段。
- Method: 提供關於類或接口上單獨某個方法(以及如何訪問該方法)的信息。所反映的方法可能是類方法或實例方法(包括抽象方法)。
- Modifier: 類提供了 static 方法和常量,對類和成員訪問修飾符進行解碼。修飾符集被表示為整數,用不同的位位置 (bit position) 表示不同的修飾符。該類的字段均是int類型的變量。
- Proxy: 提供用於創建動態代理類和實例的靜態方法,它還是由這些方法創建的所有動態代理類的超類。
- ReflectPermission:反射操作的 Permission 類。ReflectPermission 是一種指定權限,沒有動作。當前定義的唯一名稱是 suppressAccessChecks,它允許取消由反射對象在其使用點上執行的標准 Java 語言訪問檢查 - 對於 public、default(包)訪問、protected、private 成員。
當要使用反射機制去探查一個類的內部時,還可以調用getFields(),getMethods()和getConstructors()等很便利的方法。對於反射機制,和RTTI的區別就在於,RTTI是在編譯時打開和檢查.class文件,而反射機制是在運行時打開和檢查.class文件。
6,反射機制使用Demo
1.Class類是反射機制的起源,我們得到Class類對象有三種方法:
Class.forName() Object.getClass() 類字面常量xx.class
解釋一下這三種方法,
- 第一種方法是Class類自帶的方法,
- 第二種方法是所有的對象都能夠使用的方法,因為getClass()方法是Object類的方法,所有的類都繼承了Object,因此所有類的對象也都具有getClass()方法。
- 第三種方法是類字面常量。Thinking in Java中建議使用類字面常量來生成對Class對象的引用,這樣做即簡單又安全,因為在編譯時就會受到檢查,因此不需要置於try語句塊中,並且它根除了對forName()方法的調用,所以也更高效。可以想象一下JDBC的語法,在加載驅動的時候,使用的就是forName()方法,因此即使單獨這一句程序,也要使用try語句塊。
類字面常量使得創建Class對象的引用時不會自動地初始化該對象,而是按照之前提到的加載,鏈接,初始化三個步驟,這三個步驟是個懶加載的過程,不使用的時候就不加載,這種機制是C/C++無法復制模擬的。
其他DEMO
- Java反射機制demo(一)—實例化Class對象,並獲得其他類包名和類型
- Java反射機制demo(二)—通過Class實例化任意類的對象
- Java反射機制demo(三)—獲取類中的構造函數
- Java反射機制demo(四)—獲取一個類的父類和實現的接口
- Java反射機制demo(五)—獲得並調用一個類中的方法
- Java反射機制demo(六)—獲得並操作一個類的屬性