1. 程式人生 > >Java類載入器與反射

Java類載入器與反射

一個命令對應一個程序

 當我們啟動一個Java程式,即啟動一個main方法時,都將啟動一個Java虛擬機器程序,不管這個程序有多麼複雜。而不同的JVM程序之間是不會相互影響的。這也就是為什麼說,Java程式只有一個入口——main方法,讓虛擬機器呼叫。而兩個mian方法,對應的是2個JVM程序,啟動的是兩個不同的類載入器,操作的實際上是不同的類。故而不會互相影響。

類載入

 當我們使用一個類,如果這個類還未載入到記憶體中,系統會通過載入、連線、初始化對類進行初始化。

1、類載入:指的是將類的class檔案讀入JVM,併為之建立一個Class物件。

2、類連線:指的是把類的二進位制資料合併到JRE中,這又分為3個階段:

 a)、校驗:檢查載入Class檔案資料的正確性。

 b)、準備:給類的靜態變數分配儲存空間,並進行預設初始化。

 c)、解析:將類的二進位制資料中的符號引用替換成直接引用。

3、初始化:對類的靜態變數、靜態初始化塊進行初始化。

(注意:一個final型別的靜態屬性,如果在編譯時已經得到了屬性值,那麼呼叫該屬性時,不會導致該類初始化,因為這個相當於使用常量;

使用ClassLoader()方法,只是載入該類,並未初始化。)

三、類載入器。

類載入器就是負責將.class檔案載入到記憶體中,併為之生成對應的java.lang.Class物件,它負責載入所有的類,而一旦一個類被載入入JVM中,就不會被再次載入了。

在Java中,一個類用其全限定類名(即包名+類名)作為標識。

而在JVM中,一個類用其全限定類名和其類載入器作為標識。

JVM執行時會產生3個ClassLoader,分別為:BootstrapClassLoader(根類載入器)、ExtClassLoader(擴充套件類載入器)和AppClassLoader(系統類載入器)。UML結構如下:
這裡寫圖片描述

四、類載入機制。

 JVM的類載入機制主要有如下三種機制:

1、全盤負責:當一個類載入器負責載入某個Class的時候,該Class所依賴的和引用的其他Class也將由該類載入器負責載入。

2、父類委託:先委託父載入器試圖載入該類,只有在父載入器無法載入該類時才從自己的類路徑中查詢並裝載該類。

3、快取機制:快取機制保證所有被家載入過的類都會被快取,當程式中需要使用某個Class時,類載入器先從快取中搜尋。快取中搜尋不到,才會重新讀取該類對應的二進位制資料,並建立對應的Class物件。

(注意:類載入器之間的父子關係,並不是類繼承上的父子關係,而是類載入器例項之間的關係)

自定義類載入器,重寫ClassLoader的loadClass打破雙親委託,findClass遵循雙親委託,defineClass方法將位元組碼轉換成Class物件,並校驗有效性,該方法是final方法。

補充:URLClassLoader是系統類載入器和擴充套件類載入器的父類(除根類載入器之外的所有載入器都是ClassLoader子類的例項,根載入器是JVM自身實現的),它既可以從本地檔案獲取二進位制檔案載入類,也可以從遠端主機獲取二進位制檔案載入類
五、反射
在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性
以下是反射簡單用法。

1、ClassLoader物件。

可以通過2種方式獲得當前ClassLoader物件,一種是通過當前執行緒:

ClassLoader loader = Thread.currentThread().getContextClassLoader();

若已經有了Class物件,那麼可以通過Class物件的getClassLoader()方法:

ClassLoader loader2 = Xxx.class.getClassLoader();

2、Class物件。

獲得Class物件有3種方式:

 a)、Class類的forName方法,傳入全限定類名。需要丟擲異常ClassNotFoundException

 b)、呼叫該類的class屬性。

 c)、呼叫某物件的getClass()方法。

3、通過Class物件建立物件:

例如有一個Apple類。Class clazz = Class.forName(“com.stopTalking.crazy.Apple”);

該Apple類有一個預設構造器、一個String型別引數的構造器、一個String型別、int型別的構造器。

 獲得Apple物件有2種方式:

 a)、用Apple類預設構造器:Object o1 = clazz.newInstance();

 b)、用一個String型別引數的構造器:Object 02 = clazz.getConstructor(String.class).newInstance(“apple”);

 c)、用一個String型別、int型別的構造器:Object 02 = clazz.getConstructor(String.class,int.class).newInstance(“apple”,”3”);

4、呼叫方法:

Method m = clazz.getMethod(方法名,引數型別);//根據方法名和引數型別建立Method物件。

m.invokej(物件,屬性值);//根據物件和方法引數呼叫方法。

若需呼叫private方法,先呼叫Method物件的setAccessible(true);

5、訪問屬性:

Field f = clazz.getDeclaredField(屬性名);//根據屬性名獲取Field物件

f.get(物件);//根據物件得到屬性值。

6、運算元組:

Object arr = Array.newInstance(String.class,10);//建立一個簡單的String陣列。

Array.set(arr,5,”abc”);//給該陣列下標為5的元素賦值

Object name = Array.get(arr,5);//獲取該元素

六.反射和泛型

通過field物件的f.getType()方法可以獲得普通型別的field,f.getGenericType()強制轉化為ParameterizedType物件,通過getRawType()獲得原始型別,通過getActualTypeArguments()獲得泛型引數型別