一、引入
在開始反射之前,我們先看看JVM是如何将我們寫的類對應的java檔案加載到記憶體中的。
1、類的生命周期
這部分我們先講講JVM的加載機制(可能會有點難度,我盡力講的直白點)
我們寫一個最簡單的Main函數,來看看這個函數的是如何被執行的,代碼如下:(Main.java)
[java] view plain copy
- public class Main {
- public static void main(String[] args) {
- Animal animal = new Animal();
- animal.setName("cat");
- }
- public static class Animal{
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- }
這段代碼很簡單,我們定義了一個Animal的類,在main()函數中,我們首先定義了一個Animal執行個體,然後調用了該執行個體的setName()方法。
大家都知道,在拿到一個java源檔案後,如果要經過源碼編譯,要經過兩個階段:
編譯:
[java] view plain copy
- javac Main.java
在執行後後在同一目錄下生成Main.class和Animal類對應的檔案Main$Animal.class(由于我們的Animal類是Main中的内部類,是以用$表示Main類中的内部類)
運作
然後使用java Main指令運作程式:
[java] view plain copy
- java Main
在這一階段,又分為三個小階段:裝載,連結,初始化
裝載: 類的裝載是通過類加載器完成的,加載器将.class檔案的二進制檔案裝入JVM的方法區,并且在堆區建立描述這個類的java.lang.Class對象。用來封裝資料。 但是同一個類隻會被類裝載器裝載一次,記住:隻裝載一次!
連結: 連結就是把二進制資料組裝為可以運作的狀态。連結分為校驗,準備,解析這3個階段。校驗一般用來确認此二進制檔案是否适合目前的JVM(版本),準備就是為靜态成員配置設定記憶體空間,并設定預設值。解析指的是轉換常量池中的代碼作為直接引用的過程,直到所有的符号引用都可以被運作程式使用(建立完整的對應關系)。
初始化: 初始化就是對類中的變量進行初始化值;完成之後,類型也就完成了初始化,初始化之後類的對象就可以正常使用了,直到一個對象不再使用之後,将被垃圾回收。釋放空間。
當沒有任何引用指向Class對象時就會被解除安裝,結束類的生命周期。如果再次用到就再重新開始裝載、連結和初始化的過程。
上面這一大段有關類生命周期有講解,可能會有些難度,畢竟有關JVM的東東不是三言兩語能講透徹的,通過上面的這一段隻想告訴大家一點: 類隻會被裝載一次!!!!利用裝載的類可以執行個體化出各種不同的對象!
2、擷取類類型
1、泛型隐藏填充類型預設填充為無界通配符?
在上面,我們講了,類隻會被裝載一次,利用裝載的類可以執行個體化出各種不同的對象。而反射就是通過擷取裝載的類來做出各種操作的。裝載的類,我們稱為類類型,利用裝載的類産生的執行個體,我們稱為類執行個體。下面我們就看看,如何利用代碼擷取類類型的:
[java] view plain copy
- //使用方法一
- Class class1 = Animal.class;
- System.out.println(class1.getName());
- //使用方法二
- Class<?> class2= Animal.class;
- System.out.println(class2.getName());
運作結果如下:
從結果中可以看出class1和class2是完全一樣的,那構造他們時的方法一和方法二有什麼差別呢?
[java] view plain copy
- //使用方法一
- Class class1 = Animal.class;
- //使用方法二
- Class<?> class2= Animal.class;
可以看到這兩個方法,右邊全部都是Animal.class,而左邊卻有些不同。
方法一中,是直接生成了一個Class的執行個體。
而在方法二中,則生成的是一個Class的泛型,并且使用的是無界通配符來填充的。有關無界通配符的事,下面再說,這裡先講講方法一中直接生成Class對象與方法二中生成的Class泛型的差別。
我們都知道,Class類是一個泛型。而泛型的正規寫法就應該是
[java] view plain copy
- Class<Animal> class2= Animal.class;
而方法一,隻是把泛型的填充為省略了,在泛型中,如果把泛型的填充給省略掉,那就會預設填充為無界通配符?。是以方法一的真實寫法是這樣的:
[java] view plain copy
- Class<?> class1 = Animal.class;
是以這兩種寫法是意義是完全相同的。
如果我們不用通配符,也就隻能這樣寫:
[java] view plain copy
- Class<Animal> class2= Animal.class;
有關泛型和通配符的用法請參看: 《夯實JAVA基本之一 —— 泛型詳解(1):基本使用》 (上面這部分,在泛型詳解中也講過)
在本文中,為了不誤導大家,我們采用完整的Class填充方式即Class<?>
2、擷取類類型的方法
上面我們通過Class<?> class1 = Animal.class,即直接使用類名的Class對象可以擷取類類型,這隻是其中一個方法,下面這四種方法都可以獲得對應的類類型:
[java] view plain copy
- //方法一:
- Person person = new Person();
- Class a = person.getClass()
- //方法二:
- Class b = Persion.class;
- //方法三:
- Class c = Class.forName(String ClassName);
- //方法四:(不建議使用)
- Class d = context.getClassLoader().loadClass(String ClassName);
方法一中是通過類執行個體的getClass()方法得到類類型。
方法二中,直接通過類的class對象得到
方法三和方法四中是通過類名得到,這兩點要非常注意,這裡的ClassName一定要從包名具體到類名,唯一定位到一個類才行,不然就會報ClassNotFound錯誤
在上面我們提到過,類隻會被加載一次,是以a,b,c,d都是相等的,因為他們都是指向同一個對象,如果用等号操作符來判斷的話:
[java] view plain copy
- boolean result = (clazz1 == clazz2 && clazz3 == clazz4 && clazz1 == clazz3);
result的值為true;
下面我們針對方法三和方法四舉個粟子來看下:
先看一下完整的代碼結構:
可以看到我們有一個Activity:MyActivity,和一個類Animal;
我們在Activity上放一個btn,把所有代碼放在btn的點選響應中,Activity的布局難度不大就不再貼出代碼,下面僅針對類類型的代碼講解。
Animal類的定義與上方一樣,唯一不同的是,Animal類已經不再是内部類了而是單獨出來的一個類。
[java] view plain copy
- public class Animal {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
然後在Activity上btn點選時:
[java] view plain copy
- //btn點選時調用demoFunc()函數
- Button button = (Button)findViewById(R.id.btn);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- try{
- demoFunc();
- }catch (Exception e){
- System.out.print(e.getMessage());
- }
- }
- });
demoFunc()函數代碼如下:
[java] view plain copy
- public void demoFunc()throws Exception{
- Class<?> class1 = Class.forName("com.example.myReflect.Animal");
- Log.d(TAG,"通過Class.forName獲得的類名:"+class1.getName());
- class1 = getClassLoader().loadClass("com.example.myReflect.Animal");
- Log.d(TAG,"通過ClassLoader獲得的類名:"+class1.getName());
- }
其中
[java] view plain copy
- private static String TAG = "qijian";
結果如下:
從上面的用法中,可以看出,我們要使用Class.forName()或者getClassLoader().loadClass(),其中的類名必須是從包名到類名的完整路徑!
從這裡看來Class.forName()和getClassLoader().loadClass()是相同的,其實他們是有差別的,平時,我們不建議使用getClassLoader().loadClass()的方法來加載類類型。有關Class.forName()和getClassLoader().loadClass()的具體差別,會在本篇末尾講述。
二、基本類類型周邊資訊擷取
我們知道類分為基本類和泛型類,這篇我們隻講基本類類型的周邊資訊擷取,有關泛型類的周邊資訊擷取,我們會放到下一篇中。
這部分主要講述類類型周邊資訊擷取方法,包括類名,包名,超類和繼承接口。
1、類名,包名擷取
相關的有三個函數:
[java] view plain copy
- //擷取完整的類名(包含包名)
- public String getName();
- //僅擷取類名
- public String getSimpleName();
- //擷取類類型所對應的package對象
- public Package getPackage()
上面都有解釋,我們用一下這幾個函數:
[java] view plain copy
- Class<?> class1 = Animal.class;
- Package package1 = class1.getPackage();
- Log.d(TAG,"完整的類名:"+class1.getName());
- Log.d(TAG,"僅擷取類名:"+class1.getSimpleName());
- Log.d(TAG,"包名:"+package1.getName());
從結果中很清晰的看到,class.getName()擷取的是類名包含完整路徑。調用Class.forName()就是用的這個值。class.getSimpleName()得到的是僅僅是一個類名。而class.getPackage()得到的是該類對應的Package對象。通過package.getName()能獲得該類所對應的包名。
有關Package類的相關函數就不講了,基本用不到。
(2)、擷取超類Class對象
擷取superClass的類對象,涉及到兩個函數:
[java] view plain copy
- //擷取普通函數的父類Class對象
- public Class<?> getSuperclass();
- //針對泛型父類而設計
- public Type getGenericSuperclass();
getSuperclass()用來擷取普通函數,而getGenericSuperclass()是用來擷取泛型類型的父類而設計的,有關getGenericSuperclass()的知識我們後面會講,這裡先看看getSuperclass()的用法。
我們仍然利用前面講到的Animal類,然後在其上派生一個AnimalImpl子類:
[java] view plain copy
- public class Animal {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- public class AnimalImpl extends Animal {
- }
然後使用:
[java] view plain copy
- Class<?> class2 = Class.forName("com.example.myReflect.AnimalImpl");
- Class<?> parentClass = class2.getSuperclass();
- Log.d(TAG, "父類:" + parentClass.getName());
結果如下:
在這裡,我們使用了Class.forName(“com.example.myReflect.AnimalImpl”);找到AnimalImpl的類類型對象
然後調用 class2.getSuperclass()找到它的父類Class對象。很明顯,它的父類是Animal類
由于我們這裡得到了父類的Class對象parentClass,是以可以對它使用Class的一切函數。
是以調用parentClass.getName()就可以獲得父類的名稱了。
(3)、擷取類所直接繼承的接口的Class對象
這裡要先聲明一個觀點:Class類,不同于定義類的class辨別,Class類是一個泛型。類對象是由Class對象來表示,而接口對象同樣也是用Class對象來表示!
是以同樣是Class對象,它可能表示的類對象,也可能表示的是接口對象!
擷取接口對象的函數如下:
[java] view plain copy
- //擷取普通接口的方法
- public Class<?>[] getInterfaces();
- //擷取泛型接口的方法
- public Type[] getGenericInterfaces();
與擷取superClass對象一樣,這裡同樣有兩個函數來擷取接口對象,有關getGenericInterfaces()擷取泛型接口的方法,我們下篇再講,這裡先講講擷取普通接口的方法getInterfaces();
getInterfaces()将擷取指定類直接繼承的接口清單!注意一點:直接繼承!!!如果不是直接繼承,那将是擷取不到的。
我們舉個例子:
同樣,以上面的Animal為例:
我們先聲明一個接口,讓Animal類來繼承:
[java] view plain copy
- public interface IAnimal {
- void setName(String name);
- String getName();
- }
然後是Animal類繼承接口:
[java] view plain copy
- public class Animal implements IAnimal{
- private String name;
- @Override
- public String getName() {
- return name;
- }
- @Override
- public void setName(String name) {
- this.name = name;
- }
- }
為了測試不是直接繼承的接口是無法擷取的問題,我們再從Animal派生一個子類AnimalImpl:
[java] view plain copy
- public class AnimalImpl extends Animal {
- }
我們再整理一下思路,Animal類直接繼承了IAnimal,而AnimalImpl僅僅派生自Animal,它的IAnimal接口不是直接繼承的,而是從它的父類Aniaml那帶過來的
然後我們分别看看Animal類和AnimalImpl類的的擷取接口的結果,完整的代碼如下:
[java] view plain copy
- //擷取Animal類的接口清單
- Class<?> class3 = Animal.class;
- Class<?>[] interfaces = class3.getInterfaces();
- for (Class interItem:interfaces){
- Log.d(TAG, "Animal繼承的接口:" + interItem.getName());
- }
- //擷取AnimalImpl的接口清單
- class3 = AnimalImpl.class;
- interfaces = class3.getInterfaces();
- if (interfaces.length >0) {
- for (Class interItem : interfaces) {
- Log.d(TAG, "AnimalImpl繼承的接口:" + interItem.getName());
- }
- }else {
- Log.d(TAG, "AnimalImpl無繼承的接口");
- }
結果如下:
我們先看看Animal類的接口清單:
[java] view plain copy
- Class<?> class3 = Animal.class;
- Class<?>[] interfaces = class3.getInterfaces();
- for (Class interItem:interfaces){
- Log.d(TAG, "Animal繼承的接口:" + interItem.getName());
- }
我們通過class3.getInterfaces()來獲得Animal類直接繼承的接口清單,然後通過for…each列印出來。
從結果可以看出,這裡找到了Animal類所繼承的接口值。
那我們再來看看AnimalImpl的接口擷取情況又是怎樣呢:
[java] view plain copy
- class3 = AnimalImpl.class;
- interfaces = class3.getInterfaces();
- if (interfaces.length >0) {
- for (Class interItem : interfaces) {
- Log.d(TAG, "AnimalImpl繼承的接口:" + interItem.getName());
- }
- }else {
- Log.d(TAG, "AnimalImpl無繼承的接口");
- }
先通過class3 = AnimalImpl.class;獲得AnimalImpl的類類型,然後通過class3.getInterfaces()擷取AnimalImpl直接繼承的接口清單,然後列印出來。
從結果也可以看出,這裡擷取到的接口清單為空!是以這也證明了getInterfaces()隻能擷取類直接繼承的接口清單。
(4)、綜合提升:擷取某個類類型的所有接口
現在我們提升一下,如果我想傳進去一下類類型,然後要得到它所有繼承的接口清單要怎麼辦?(不管它是不是直接繼承來的都要列出來)
那隻有靠遞規了,我們需要遞規它的父類直接繼承的接口、父類的父類直接繼承的接口以此類推,最終到Object類的時候就找到所有繼承的接口了
在開始遞規擷取所有接口之前,我們先構造下代碼。
由于我們要擷取所有接口,為了效果更好些,我們在Animal和AnimalImpl基礎上,多加幾個繼承的接口:
[java] view plain copy
- //給Animal添加 IAnimal,Serializable兩個接口
- public class Animal implements IAnimal,Serializable{
- private String name;
- @Override
- public String getName() {
- return name;
- }
- @Override
- public void setName(String name) {
- this.name = name;
- }
- }
- //給AnimalImpl添加Serializable接口
- public class AnimalImpl extends Animal implements Serializable {
- }
是以如果我們擷取AnimalImpl類的接口清單,得到的應該是三個:自已直接繼承的Serializable,從父類Animal那繼承的IAnimal和Serializable
好了,言規正轉,看擷取類類型所有接口清單的方法:
[java] view plain copy
- public Class<?>[] getAllInterface(Class<?> clazz){
- //擷取自身的所有接口
- Class<?>[] interSelf = clazz.getInterfaces();
- //遞規調用getAllInterface擷取超類的所有接口
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
- //傳回值
- if (interParent == null && interSelf != null){
- return interSelf;
- }else if (interParent == null && interSelf == null){
- return null;
- }else if (interParent != null && interSelf == null){
- return interParent;
- }else {
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
- }
- }
- //調用
- Class<?>[] clazzes = getAllInterface(AnimalImpl.class);
- SpannableStringBuilder builder = new SpannableStringBuilder();
- for (Class clazz:clazzes){
- builder.append(clazz.getName());
- builder.append(" ");
- }
- Log.d(TAG, "AnimalImpl繼承的所有接口:"+ builder.toString());
先看看執行結果:
這段代碼最關鍵的地方在于getAllInterface(Class<?> clazz);我們來看看這個函數是如何遞規得到所有接口的數組的。
[java] view plain copy
- public Class<?>[] getAllInterface(Class<?> clazz){
- //擷取自身的所有接口
- Class<?>[] interSelf = clazz.getInterfaces();
- //遞規調用getAllInterface擷取超類的所有接口
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
- //傳回值
- if (interParent == null && interSelf != null){
- return interSelf;
- }else if (interParent == null && interSelf == null){
- return null;
- }else if (interParent != null && interSelf == null){
- return interParent;
- }else {
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
- }
- }
這段代碼分為兩部分,第一部分是獲得自己的接口清單和父類的清單:
[java] view plain copy
- //擷取自身的所有接口
- Class<?>[] interSelf = clazz.getInterfaces();
- //遞規調用getAllInterface擷取超類的所有接口
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
首先通過Class<?>[] interSelf = clazz.getInterfaces();獲得自已直接繼承的接口清單。這個很好了解,可能對于有些同學而言,難就難在擷取父類清單的過程:
[java] view plain copy
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
在這段代碼中,首先,通過Class<?> superClazz = clazz.getSuperclass();擷取父類的Class類型,然後調用getAllInterface(superClazz)獲得父類的所有接口清單。
有些同學不解了,getAllInterface(superClazz)這不是目前函數自己嗎?是的,我們寫getAllInterface(superClazz)是來幹什麼的,就是用來擷取傳進去的類的所有接口,是以我們把父類傳進去,當然也能獲得它父類的所有接口清單了。(有關遞規的知識,可能是有些難度的,遞規不是本文重點,不做詳細介紹,有疑問的同學可以搜搜這方面文章補充下)
我們再重複一遍,我們的getAllInterface(Class<?> clazz)函數,會傳回clazz對象的所有接口清單。現在我們得到了它自己直接繼承的接口,也有它父類的所有接口清單。那麼,把它們兩個合并,就是所有的接口清單了。
是以下面是接口清單傳回的代碼:
[java] view plain copy
- if (interParent == null && interSelf != null){
- return interSelf;
- }else if (interParent == null && interSelf == null){
- return null;
- }else if (interParent != null && interSelf == null){
- return interParent;
- }else {
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
- }
首先,對interParent和interSelf判空,如果兩個清單都是空,那直接傳回空;如果有一個是空,另一個不是空,則傳回那個不是空的清單,如果兩個都不是空,則将他們合并,然後傳回合并後的清單。
有點難度的地方,可能是當兩個都不為空的時候,合并時的代碼:
[java] view plain copy
- final int length = interParent.length + interSelf.length;
- Class<?>[] result = new Class[length];
- System.arraycopy(interSelf,0,result,0,interSelf.length);
- System.arraycopy(interParent,0,result,interSelf.length,interParent.length);
- return result;
這裡是先根據interParent和interSelf的長度,生成一個它倆總長的一個數組result;
然後通過System.arraycopy()函數,将它們兩個複制到result數組中。
System.arraycopy()的定義及釋義如下:
[java] view plain copy
- public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);
最後就是使用getAllInterface()函數啦:
[java] view plain copy
- Class<?>[] clazzes = getAllInterface(AnimalImpl.class);
- SpannableStringBuilder builder = new SpannableStringBuilder();
- for (Class clazz:clazzes){
- builder.append(clazz.getName());
- builder.append(" ");
- }
- Log.d(TAG, "AnimalImpl繼承的所有接口:"+ builder.toString());
這段代碼很好了解,先擷取AnimalImpl的所有接口清單,然後使用SpannableStringBuilder将它們拼接成一個String字元串,然後列印出來。
到這裡,基本類的周邊資訊擷取就結束了,下面我們來看看泛型類的周邊資訊擷取要怎麼來做。
(5)、擷取類的通路修飾符
由于我們在定義類時,比如下面的内部類:
[java] view plain copy
- public static final class InnerClass{
- }
在類名,前面的那一坨public static final,就是類的通路修飾符,是定義這個類在的通路區域和通路限定的。這部分就講講如何擷取類的這部分通路修飾符
我們先看一個例子(我們以上面的内部類InnerClass為例):
[java] view plain copy
- Class<?> clazz = getClassLoader().loadClass(InnerClass.class.getName());
- int modifiers = clazz.getModifiers();
- String retval = Modifier.toString(modifiers);
- boolean isFinal = Modifier.isFinal(modifiers);
- Log.d(TAG, "InnerClass的定義修飾符:" + retval);
- Log.d(TAG, "is Final:" + isFinal);
結果如下:
首先,在這部分代碼中,我們又換了一種類加載方式,使用的是ClassLoader;
然後我們單獨來看看這句:
[java] view plain copy
- int modifiers = clazz.getModifiers();
通過clazz.getModifiers()得到一個整型變量,由于通路修飾符有很多,是以這些修飾符被打包成一個int,對應的二進制中,每個修飾符是一個标志位,可以被置位或清零。
另外Java開發人員單獨提供了一個類來提取這個整型變量中各辨別位的函數,這個類就是Modifier
Modifier中主要有以下幾個方法:
[java] view plain copy
- //根據整型變量來生成對應的修飾符字元串
- String Modifier.toString(int modifiers)
- //以下這些方法來檢查特定的修飾符是否存在
- boolean Modifier.isAbstract(int modifiers)
- boolean Modifier.isFinal(int modifiers)
- boolean Modifier.isInterface(int modifiers)
- boolean Modifier.isNative(int modifiers)
- boolean Modifier.isPrivate(int modifiers)
- boolean Modifier.isProtected(int modifiers)
- boolean Modifier.isPublic(int modifiers)
- boolean Modifier.isStatic(int modifiers)
- boolean Modifier.isStrict(int modifiers)
- boolean Modifier.isSynchronized(int modifiers)
- boolean Modifier.isTransient(int modifiers)
- boolean Modifier.isVolatile(int modifiers)
首先是toString函數:
[java] view plain copy
- String Modifier.toString(int modifiers)
這個函數的作用就是根據傳進來的整型,根據其中的辨別位來判斷具有哪個修飾符,然後将所有修飾符拼接起來輸出。比如我們的例子中輸出的就是:public static final
其它的就是一些isXXXX(int moifiers)的判斷指定辨別位的函數了,沒什麼難度
在例子中,我們使用了Modifier.isFinal(int modifiers)來判斷是不是具有final修飾符,傳回結果為true;
從Modifier類的isXXX()系列函數中,可以看到它的修飾符确實很多,但這些修飾符,有些類是根本用不到的,比如isNative()等,這是因為不光類會使用Modifier類來判斷通路修飾符,接口,成員變量和成員函數所對應的類型也同樣都是使用Modifier來判斷通路修飾符的,這些我們後面在講到的時候,都會說到!
(6)、擷取接口的通路修飾符
從上面擷取類的通路修飾符時,我們講過,接口,類,函數都是通過Modifier類判斷通路修飾符的,又因為類和接口類型全部都是用Class對象來辨別,是以接口和類的擷取通路修飾符的方式完全相同,下面就舉一個簡單的例子:
[java] view plain copy
- //定義一個類部接口
- public static interface InnerInteface{
- }
- //使用
- Class<?> clazz2 = InnerInteface.class;
- int modifiers = clazz2.getModifiers();
- String retval = Modifier.toString(modifiers);
- boolean isInteface = Modifier.isInterface(modifiers);
- Log.d(TAG, "InnerClass的定義修飾符:" + retval);
- Log.d(TAG, "isInteface:" + isInteface);
這裡首先要注意的一點是:
[java] view plain copy
- Class<?> clazz2 = InnerInteface.class;
如果我們要直接擷取一個接口的對象,同樣,也是通過開頭所講的那四種擷取Class對象的方式。
因為我們現在知道Class對象,不光代表類也可以代表接口。
下面有關Modifier的使用與第五部分擷取類的修飾符是一樣的,就不再細講。
(7)Class.forName(String className)與ClassLoader.loadClass(String ClassName)的差別
我們通過源碼來看看他們的差別:
先看Class.forName:
[java] view plain copy
- public static Class<?> forName(String className) throws ClassNotFoundException {
- return forName(className, true, VMStack.getCallingClassLoader());
- }
- public static Class<?> forName(String className, boolean initializeBoolean,
- ClassLoader classLoader) throws ClassNotFoundException {
- Class<?> result;
- try {
- result = classForName(className, initializeBoolean,
- classLoader);
- } catch (ClassNotFoundException e) {
- Throwable cause = e.getCause();
- if (cause instanceof ExceptionInInitializerError) {
- throw (ExceptionInInitializerError) cause;
- }
- throw e;
- }
- return result;
- }
從源中可以看到Class.forName(String className),最終調用的是forName(String className, boolean initializeBoolean,ClassLoader classLoader)
其中:
- className:類名
- initializeBoolean:表示是否需要初始化;如果設為true,表示在加載以後,還會進傳入連結接階段
- classLoader:ClassLoader加載器
我們知道源檔案在編譯後,在運作時,分為三個階段,加載,連結和初始化。這裡的initializeBoolean就是定義是否進行連結和初始化。而Class.forName預設是設定的為true,是以利用Class.forName()得到的類類型,除了加載進來以外,還進行了連結和初始化操作。
下面再來看看ClassLoader.loadClass()
[java] view plain copy
- public Class<?> loadClass(String className) throws ClassNotFoundException {
- return loadClass(className, false);
- }
- protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
- Class<?> clazz = findLoadedClass(className);
- if (clazz == null) {
- try {
- clazz = parent.loadClass(className, false);
- } catch (ClassNotFoundException e) {
- // Don't want to see this.
- }
- if (clazz == null) {
- clazz = findClass(className);
- }
- }
- return clazz;
- }
loadClass(String className)最終是調用遞規函數loadClass(String className, boolean resolve)來将類加載出來。
通過代碼也可以看出來ClassLoader的loadClass(String className)隻是将類加載出來,并沒有連結與初始化的步驟。是以這就是它們的差別
最後,我們總結一下,Class.forName(String className)不僅會将類加載進來,而且會對其進行初始化,而ClassLoader.loadClass(String ClassName)則隻是将類加載進來,而沒有對類進行初始化。一般來講,他們兩個是通用的,但如果你加載類依賴初始化值的話,那ClassLoader.loadClass(String ClassName)将不再适用。
舉例來說:
在JDBC程式設計中,常看到這樣的用法,Class.forName(“com.mysql.jdbc.Driver”),如果換成了getClass().getClassLoader().loadClass(“com.mysql.jdbc.Driver”),就不行。
為什麼呢?打開com.mysql.jdbc.Driver的源代碼看看,
[java] view plain copy
- // Register ourselves with the DriverManager
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- }
原來,Driver在static塊中會注冊自己到java.sql.DriverManager。而static塊就是在Class的初始化中被執行。是以這個地方就隻能用Class.forName(className)。
好了,這篇就到這了,内容不太多,但比較複雜。最後我們再總結一下這篇文章所涉及到的幾個函數:
[java] view plain copy
- //擷取類類型對象的幾種方式:
- Person person = new Person();
- Class a = person.getClass() //方法一:
- Class b = Persion.class;//方法二:
- Class c = Class.forName(String ClassName); //方法三:
- Class d = context.getClassLoader().loadClass(String ClassName);//方法四:(不建議使用)
- //擷取包名類名
- public String getName();//擷取完整的類名(包含包名)
- public String getSimpleName();//僅擷取類名
- public Package getPackage()//擷取類類型所對應的package對象
- //擷取超類Class對象
- public Class<?> getSuperclass();//擷取普通函數的父類Class對象
- public Type getGenericSuperclass();//針對泛型父類而設計(下篇講解)
- //擷取接口Class對象
- public Class<?>[] getInterfaces();//擷取普通接口的方法
- public Type[] getGenericInterfaces();//擷取泛型接口的方法
- //類通路修飾符
- int modifiers = clazz.getModifiers();//擷取類通路修飾符對應的int變量
- String Modifier.toString(int modifiers) //根據整型變量來生成對應的修飾符字元串
- boolean Modifier.isAbstract(int modifiers)//isXXX()系列函數用以檢查特定的修飾符是否存在