從java虛擬機器基礎看java類的反射機制
java虛擬機器相關基礎
任何一種語言編寫的程式,執行在不同的系統上,最終都需要被編譯成為機器可以識別的機器碼(也就是01010…1這種二進位制資料)。對於java語言而言,虛擬機器起到了機器語言與該語言自身的橋樑作用,虛擬機器可以識別字節碼,針對不同的作業系統,可以有適應不同機器、作業系統的虛擬機器,如此一來,程式設計師編寫的程式就可以通過虛擬機器無差異的執行在不同系統上,而不是針對不同的機器系統要求分別編寫符合要求的程式碼。 java原始碼在被編譯後生成.class檔案,該檔案是按照虛擬機器規範編寫的位元組碼(8位元位為單位編譯後的檔案),最終檔案執行在虛擬機器上,虛擬機器以位元組為單位解釋執行,這種先編譯成位元組碼,再由虛擬機器對位元組碼進行動態解析執行的語言,相較於其他編譯語言(以c++為例,是編譯型語言,通過編譯連線後是可以直接執行在機器上的),它歷經兩個過程,一半編譯一半解釋,稱其為半解釋型語言。 class檔案作為一種完全按照虛擬機器規範編譯生成的檔案,在被虛擬機器載入後才能進入執行時資料區的方法區中,在這裡需要區分兩個概念:class檔案的結構;虛擬機器執行時資料區。 class檔案是一組以8位位元組為基礎單位的二進位制流,根據虛擬機器規範,採用一種類似於C語言結構體的偽結構來儲存資料,這種結構只有兩種資料型別:無符號數和表。由這兩種資料結構構成了class檔案的各種資料項:魔數、常量池、訪問標誌、類索引、父類索引、介面索引、欄位表集合、方法表集合、屬性表集合等。這些資料項按照虛擬機器的規範緊湊的排列在class檔案中,不使用任何分隔符。 虛擬機器執行時資料區域是指虛擬機器在執行java程式時,會將虛擬機器自身所管理的記憶體劃分為若干用途各不相同的資料區域,具體資料區域如下圖所示:
class檔案中描述的各種資訊,最終都需要載入到虛擬機器中之後才能執行和被使用,這就涉及到類的載入機制。整個類載入過程包含載入、驗證、準備、解析、初始化、使用和解除安裝,簡單來講,載入階段根據類的全限定名獲取其對應的位元組流,將此位元組流的靜態儲存結構轉換為虛擬機器的執行時資料結構(其實就是執行時資料區的方法區),在方法區中生成代表該類的java.lang.class物件;驗證階段主要是確保Class檔案的位元組流中包含的資訊符合當前虛擬機器要求;準備階段正式為類變數分配記憶體並設定類變數初始值(資料型別的零值);解析階段是指虛擬機器將class檔案中常量池的符號引用替換為直接引用的過程;初始化階段,開始真正執行類中定義的java程式程式碼。
java的反射機制
驗證、準備與解析三個階段就相當於完全編譯語言中的連線階段,在java中這個連線階段是在類載入過程中完成的,而編譯語言中是在編譯階段就完成的。前面講了那麼多虛擬機器相關的內容,似乎與虛擬機器關係不大,非也非也。通過前面內容我們大體知道了原始檔被編譯成位元組碼,位元組碼被虛擬機器使用,因此程式正常執行的前提是類檔案被載入,在原始檔被編譯時就已經確切知道類的存在,這就是典型的RTTI(執行時型別確定),利用class物件,可以瞭解物件和類執行時的資訊。但是反射機制打破了這種在編譯時必須知道具體類資訊之後才可以使用該類的約束,Class類與java.lang.reflect類庫一起對反射的概念進行了支援,可以在執行時動態獲取相關類資訊,例如可以利用反射類庫的一些類來實現建立物件(Constructor類),具體我們來看一個例子:
package javaTest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.swing.JOptionPane;
public class ReflectTest1 {
public ReflectTest1() {
String classinfo=JOptionPane.showInputDialog(null,"輸入類全路徑");
try {
Class<?> class1=Class.forName(classinfo);
Method[] methods=class1.getDeclaredMethods();
for(Method med:methods) {
System.out.println(med.toString());
}
System.out.println("************************8");
Field[] fields=class1.getDeclaredFields();
for(Field fid:fields) {
System.out.println(fid.toString());
}
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("程式順利結束。");
}
public static void main(String[] args) {
new ReflectTest1();
}
}
執行以上程式碼,結果如下:
可以看到,程式正常執行,這說明編譯是通過的,現在我們輸入類的全路徑:
最終控制檯輸出如下:
public void javax.swing.JFrame.remove(java.awt.Component)
public void javax.swing.JFrame.update(java.awt.Graphics)
protected java.lang.String javax.swing.JFrame.paramString()
public javax.accessibility.AccessibleContext javax.swing.JFrame.getAccessibleContext()
public java.awt.Container javax.swing.JFrame.getContentPane()
public javax.swing.JRootPane javax.swing.JFrame.getRootPane()
public static boolean javax.swing.JFrame.isDefaultLookAndFeelDecorated()
public void javax.swing.JFrame.setLayout(java.awt.LayoutManager)
public java.awt.Graphics javax.swing.JFrame.getGraphics()
public javax.swing.TransferHandler javax.swing.JFrame.getTransferHandler()
public void javax.swing.JFrame.repaint(long,int,int,int,int)
public void javax.swing.JFrame.setTransferHandler(javax.swing.TransferHandler)
protected void javax.swing.JFrame.addImpl(java.awt.Component,java.lang.Object,int)
protected javax.swing.JRootPane javax.swing.JFrame.createRootPane()
public int javax.swing.JFrame.getDefaultCloseOperation()
protected boolean javax.swing.JFrame.isRootPaneCheckingEnabled()
public void javax.swing.JFrame.setDefaultCloseOperation(int)
public static void javax.swing.JFrame.setDefaultLookAndFeelDecorated(boolean)
protected void javax.swing.JFrame.setRootPane(javax.swing.JRootPane)
protected void javax.swing.JFrame.setRootPaneCheckingEnabled(boolean)
protected void javax.swing.JFrame.frameInit()
public java.awt.Component javax.swing.JFrame.getGlassPane()
public javax.swing.JMenuBar javax.swing.JFrame.getJMenuBar()
public javax.swing.JLayeredPane javax.swing.JFrame.getLayeredPane()
public void javax.swing.JFrame.setContentPane(java.awt.Container)
public void javax.swing.JFrame.setGlassPane(java.awt.Component)
public void javax.swing.JFrame.setJMenuBar(javax.swing.JMenuBar)
public void javax.swing.JFrame.setLayeredPane(javax.swing.JLayeredPane)
protected void javax.swing.JFrame.processWindowEvent(java.awt.event.WindowEvent)
public void javax.swing.JFrame.setIconImage(java.awt.Image)
***********************************************************************************************
public static final int javax.swing.JFrame.EXIT_ON_CLOSE
private static final java.lang.Object javax.swing.JFrame.defaultLookAndFeelDecoratedKey
private int javax.swing.JFrame.defaultCloseOperation
private javax.swing.TransferHandler javax.swing.JFrame.transferHandler
protected javax.swing.JRootPane javax.swing.JFrame.rootPane
protected boolean javax.swing.JFrame.rootPaneCheckingEnabled
protected javax.accessibility.AccessibleContext javax.swing.JFrame.accessibleContext
程式順利結束。
通過以上例子我們可以體會到反射的強大之處:可以在執行時動態載入類,使用類的相關資訊,即便是利用Class類動態載入的類不存在,程式也可以捕獲處理異常,不會影響程式的正常執行。 再次列舉一個例子幫助理解反射的作用: 在合作開發中有兩個程式設計師,一個程式設計師在寫程式的時候,需要使用第二個程式設計師所寫的類,但是第二個程式設計師並沒有完成其類的構建,這種情況下第一個程式設計師的程式碼就不能通過編譯。但是利用反射機制,第一個程式設計師就可以完成其程式碼的編譯,從而可以進行相關測試,如此可以大大的減小程式設計師之間的相互約束。