Java 反射(1):基本類周邊信息獲取

一、引入

在開始反射之前,我們先看看JVM是如何將我們寫的類對應的java文件加載到內存中的。

1、類的生命週期

這部分我們先講講JVM的加載機制(可能會有點難度,我盡力講的直白點)
我們寫一個最簡單的Main函數,來看看這個函數的是如何被執行的,代碼如下:(Main.java)

[java] view plaincopy
  1. public class Main {  
  2.     public static void main(String[] args)  {  
  3.         Animal animal = new Animal();  
  4.         animal.setName("cat");  
  5.     }  
  6.   
  7.     public static class Animal{  
  8.         private String name;  
  9.   
  10.         public String getName() {  
  11.             return name;  
  12.         }  
  13.   
  14.         public void setName(String name) {  
  15.             this.name = name;  
  16.         }  
  17.     }  
  18. }  
這段代碼很簡單,我們定義了一個Animal的類,在main()函數中,我們首先定義了一個Animal實例,然後調用了該實例的setName()方法。
大家都知道,在拿到一個java源文件後,如果要經過源碼編譯,要經過兩個階段:
編譯:
[java] view plaincopy
  1. javac Main.java  
在執行後後在同一目錄下生成Main.class和Animal類對應的文件Main$Animal.class(由於我們的Animal類是Main中的內部類,所以用$表示Main類中的內部類)
運行
然後使用java Main命令運行程序:
[java] view plaincopy
  1. java Main  
在這一階段,又分爲三個小階段:裝載,鏈接,初始化

裝載:類的裝載是通過類加載器完成的,加載器將.class文件的二進制文件裝入JVM的方法區,並且在堆區創建描述這個類的java.lang.Class對象。用來封裝數據。 但是同一個類只會被類裝載器裝載一次,記住:只裝載一次!

鏈接:鏈接就是把二進制數據組裝爲可以運行的狀態。鏈接分爲校驗,準備,解析這3個階段。校驗一般用來確認此二進制文件是否適合當前的JVM(版本),準備就是爲靜態成員分配內存空間,並設置默認值。解析指的是轉換常量池中的代碼作爲直接引用的過程,直到所有的符號引用都可以被運行程序使用(建立完整的對應關係)。

初始化:初始化就是對類中的變量進行初始化值;完成之後,類型也就完成了初始化,初始化之後類的對象就可以正常使用了,直到一個對象不再使用之後,將被垃圾回收。釋放空間。
當沒有任何引用指向Class對象時就會被卸載,結束類的生命週期。如果再次用到就再重新開始裝載、鏈接和初始化的過程。
上面這一大段有關類生命週期有講解,可能會有些難度,畢竟有關JVM的東東不是三言兩語能講透徹的,通過上面的這一段只想告訴大家一點:類只會被裝載一次!!!!利用裝載的類可以實例化出各種不同的對象!

2、獲取類類型

1、泛型隱藏填充類型默認填充爲無界通配符?

在上面,我們講了,類只會被裝載一次,利用裝載的類可以實例化出各種不同的對象。而反射就是通過獲取裝載的類來做出各種操作的。裝載的類,我們稱爲類類型,利用裝載的類產生的實例,我們稱爲類實例。下面我們就看看,如何利用代碼獲取類類型的:

[java] view plaincopy
  1. //使用方法一  
  2. Class class1 = Animal.class;  
  3. System.out.println(class1.getName());  
  4. //使用方法二  
  5. Class<?> class2= Animal.class;  
  6. System.out.println(class2.getName());  
運行結果如下:

從結果中可以看出class1和class2是完全一樣的,那構造他們時的方法一和方法二有什麼區別呢?

[java] view plaincopy
  1. //使用方法一  
  2. Class class1 = Animal.class;  
  3. //使用方法二  
  4. Class<?> class2= Animal.class;  
可以看到這兩個方法,右邊全部都是Animal.class,而左邊卻有些不同。
方法一中,是直接生成了一個Class的實例。
而在方法二中,則生成的是一個Class的泛型,並且使用的是無界通配符來填充的。有關無界通配符的事,下面再說,這裏先講講方法一中直接生成Class對象與方法二中生成的Class泛型的區別。
我們都知道,Class類是一個泛型。而泛型的正規寫法就應該是
[java] view plaincopy
  1. Class<Animal> class2= Animal.class;  
而方法一,只是把泛型的填充爲省略了,在泛型中,如果把泛型的填充給省略掉,那就會默認填充爲無界通配符?。所以方法一的真實寫法是這樣的:
[java] view plaincopy
  1. Class<?> class1 = Animal.class;  
所以這兩種寫法是意義是完全相同的。
如果我們不用通配符,也就只能這樣寫:
[java] view plaincopy
  1. Class<Animal> class2= Animal.class;  
有關泛型和通配符的用法請參看:《夯實JAVA基本之一 —— 泛型詳解(1):基本使用》(上面這部分,在泛型詳解中也講過)
在本文中,爲了不誤導大家,我們採用完整的Class填充方式即Class<?>

2、獲取類類型的方法

上面我們通過Class<?> class1 = Animal.class,即直接使用類名的Class對象可以獲取類類型,這只是其中一個方法,下面這四種方法都可以獲得對應的類類型:

[java] view plaincopy
  1. //方法一:  
  2. Person person = new Person();    
  3. Class a = person.getClass()   
  4. //方法二:  
  5. Class b = Persion.class;  
  6. //方法三:  
  7. Class c = Class.forName(String ClassName);   
  8. //方法四:(不建議使用)  
  9. Class d = context.getClassLoader().loadClass(String ClassName);  
方法一中是通過類實例的getClass()方法得到類類型。
方法二中,直接通過類的class對象得到
方法三和方法四中是通過類名得到,這兩點要非常注意,這裏的ClassName一定要從包名具體到類名,唯一定位到一個類才行,不然就會報ClassNotFound錯誤
在上面我們提到過,類只會被加載一次,所以a,b,c,d都是相等的,因爲他們都是指向同一個對象,如果用等號操作符來判斷的話:
[java] view plaincopy
  1. boolean result = (clazz1 == clazz2 && clazz3 == clazz4 && clazz1 == clazz3);  
result的值爲true;
下面我們針對方法三和方法四舉個粟子來看下:
先看一下完整的代碼結構:

可以看到我們有一個Activity:MyActivity,和一個類Animal;
我們在Activity上放一個btn,把所有代碼放在btn的點擊響應中,Activity的佈局難度不大就不再貼出代碼,下面僅針對類類型的代碼講解。
Animal類的定義與上方一樣,唯一不同的是,Animal類已經不再是內部類了而是單獨出來的一個類。
[java] view plaincopy
  1. public class Animal {  
  2.     private String name;  
  3.   
  4.     public String getName() {  
  5.         return name;  
  6.     }  
  7.   
  8.     public void setName(String name) {  
  9.         this.name = name;  
  10.     }  
  11. }  
然後在Activity上btn點擊時:
[java] view plaincopy
  1. //btn點擊時調用demoFunc()函數  
  2. Button button = (Button)findViewById(R.id.btn);  
  3. button.setOnClickListener(new View.OnClickListener() {  
  4.     @Override  
  5.     public void onClick(View v) {  
  6.         try{  
  7.             demoFunc();  
  8.         }catch (Exception e){  
  9.             System.out.print(e.getMessage());  
  10.         }  
  11.     }  
  12. });  
demoFunc()函數代碼如下:
[java] view plaincopy
  1. public void demoFunc()throws Exception{  
  2.     Class<?> class1 = Class.forName("com.example.myReflect.Animal");  
  3.     Log.d(TAG,"通過Class.forName獲得的類名:"+class1.getName());  
  4.   
  5.     class1 = getClassLoader().loadClass("com.example.myReflect.Animal");  
  6.     Log.d(TAG,"通過ClassLoader獲得的類名:"+class1.getName());  
  7. }  
其中
[java] view plaincopy
  1. private static String TAG = "qijian";  
結果如下:

從上面的用法中,可以看出,我們要使用Class.forName()或者getClassLoader().loadClass(),其中的類名必須是從包名到類名的完整路徑!
從這裏看來Class.forName()和getClassLoader().loadClass()是相同的,其實他們是有區別的,平時,我們不建議使用getClassLoader().loadClass()的方法來加載類類型。有關Class.forName()和getClassLoader().loadClass()的具體區別,會在本篇末尾講述。

二、基本類類型周邊信息獲取


我們知道類分爲基本類和泛型類,這篇我們只講基本類類型的周邊信息獲取,有關泛型類的周邊信息獲取,我們會放到下一篇中。
這部分主要講述類類型周邊信息獲取方法,包括類名,包名,超類和繼承接口。

1、類名,包名獲取

相關的有三個函數:
[java] view plaincopy
  1. //獲取完整的類名(包含包名)  
  2. public String getName();  
  3. //僅獲取類名  
  4. public String getSimpleName();  
  5. //獲取類類型所對應的package對象  
  6. public Package getPackage()  
上面都有解釋,我們用一下這幾個函數:
[java] view plaincopy
  1. Class<?> class1 = Animal.class;  
  2. Package package1 = class1.getPackage();  
  3.   
  4. Log.d(TAG,"完整的類名:"+class1.getName());  
  5. Log.d(TAG,"僅獲取類名:"+class1.getSimpleName());  
  6. Log.d(TAG,"包名:"+package1.getName());  

從結果中很清晰的看到,class.getName()獲取的是類名包含完整路徑。調用Class.forName()就是用的這個值。class.getSimpleName()得到的是僅僅是一個類名。而class.getPackage()得到的是該類對應的Package對象。通過package.getName()能獲得該類所對應的包名。
有關Package類的相關函數就不講了,基本用不到。

(2)、獲取超類Class對象

獲取superClass的類對象,涉及到兩個函數:
[java] view plaincopy
  1. //獲取普通函數的父類Class對象  
  2. public Class<?> getSuperclass();  
  3. //針對泛型父類而設計  
  4. public Type getGenericSuperclass();  
getSuperclass()用來獲取普通函數,而getGenericSuperclass()是用來獲取泛型類型的父類而設計的,有關getGenericSuperclass()的知識我們後面會講,這裏先看看getSuperclass()的用法。
我們仍然利用前面講到的Animal類,然後在其上派生一個AnimalImpl子類:
[java] view plaincopy
  1. public class Animal {  
  2.     private String name;  
  3.   
  4.     public String getName() {  
  5.         return name;  
  6.     }  
  7.   
  8.     public void setName(String name) {  
  9.         this.name = name;  
  10.     }  
  11. }  
  12.   
  13. public class AnimalImpl extends Animal {  
  14. }  
然後使用:
[java] view plaincopy
  1. Class<?> class2 = Class.forName("com.example.myReflect.AnimalImpl");  
  2. Class<?> parentClass = class2.getSuperclass();  
  3. 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 plaincopy
  1. //獲取普通接口的方法  
  2. public Class<?>[] getInterfaces();  
  3. //獲取泛型接口的方法  
  4. public Type[] getGenericInterfaces();  
與獲取superClass對象一樣,這裏同樣有兩個函數來獲取接口對象,有關getGenericInterfaces()獲取泛型接口的方法,我們下篇再講,這裏先講講獲取普通接口的方法getInterfaces();
getInterfaces()將獲取指定類直接繼承的接口列表!注意一點:直接繼承!!!如果不是直接繼承,那將是獲取不到的。
我們舉個例子:
同樣,以上面的Animal爲例:
我們先聲明一個接口,讓Animal類來繼承:
[java] view plaincopy
  1. public interface IAnimal {  
  2.     void setName(String name);  
  3.     String getName();  
  4. }  
然後是Animal類繼承接口:
[java] view plaincopy
  1. public class Animal implements IAnimal{  
  2.     private String name;  
  3.   
  4.     @Override  
  5.     public String getName() {  
  6.         return name;  
  7.     }  
  8.   
  9.     @Override  
  10.     public void setName(String name) {  
  11.         this.name = name;  
  12.     }  
  13. }  
爲了測試不是直接繼承的接口是無法獲取的問題,我們再從Animal派生一個子類AnimalImpl:
[java] view plaincopy
  1. public class AnimalImpl extends Animal {  
  2. }  
我們再整理一下思路,Animal類直接繼承了IAnimal,而AnimalImpl僅僅派生自Animal,它的IAnimal接口不是直接繼承的,而是從它的父類Aniaml那帶過來的
然後我們分別看看Animal類和AnimalImpl類的的獲取接口的結果,完整的代碼如下:
[java] view plaincopy
  1. //獲取Animal類的接口列表  
  2. Class<?> class3 = Animal.class;  
  3. Class<?>[] interfaces = class3.getInterfaces();  
  4. for (Class interItem:interfaces){  
  5.     Log.d(TAG, "Animal繼承的接口:" + interItem.getName());  
  6. }  
  7.   
  8. //獲取AnimalImpl的接口列表  
  9. class3 = AnimalImpl.class;  
  10. interfaces = class3.getInterfaces();  
  11. if (interfaces.length >0) {  
  12.     for (Class interItem : interfaces) {  
  13.         Log.d(TAG, "AnimalImpl繼承的接口:" + interItem.getName());  
  14.     }  
  15. }else {  
  16.     Log.d(TAG, "AnimalImpl無繼承的接口");  
  17. }  
結果如下:

我們先看看Animal類的接口列表:

[java] view plaincopy
  1. Class<?> class3 = Animal.class;  
  2. Class<?>[] interfaces = class3.getInterfaces();  
  3. for (Class interItem:interfaces){  
  4.     Log.d(TAG, "Animal繼承的接口:" + interItem.getName());  
  5. }  
我們通過class3.getInterfaces()來獲得Animal類直接繼承的接口列表,然後通過for…each打印出來。
從結果可以看出,這裏找到了Animal類所繼承的接口值。
那我們再來看看AnimalImpl的接口獲取情況又是怎樣呢:
[java] view plaincopy
  1. class3 = AnimalImpl.class;  
  2. interfaces = class3.getInterfaces();  
  3. if (interfaces.length >0) {  
  4.     for (Class interItem : interfaces) {  
  5.         Log.d(TAG, "AnimalImpl繼承的接口:" + interItem.getName());  
  6.     }  
  7. }else {  
  8.     Log.d(TAG, "AnimalImpl無繼承的接口");  
  9. }  
先通過class3 = AnimalImpl.class;獲得AnimalImpl的類類型,然後通過class3.getInterfaces()獲取AnimalImpl直接繼承的接口列表,然後打印出來。
從結果也可以看出,這裏獲取到的接口列表爲空!所以這也證明了getInterfaces()只能獲取類直接繼承的接口列表。

(4)、綜合提升:獲取某個類類型的所有接口

現在我們提升一下,如果我想傳進去一下類類型,然後要得到它所有繼承的接口列表要怎麼辦?(不管它是不是直接繼承來的都要列出來)
那只有靠遞規了,我們需要遞規它的父類直接繼承的接口、父類的父類直接繼承的接口以此類推,最終到Object類的時候就找到所有繼承的接口了
在開始遞規獲取所有接口之前,我們先構造下代碼。
由於我們要獲取所有接口,爲了效果更好些,我們在Animal和AnimalImpl基礎上,多加幾個繼承的接口:
[java] view plaincopy
  1. //給Animal添加 IAnimal,Serializable兩個接口  
  2. public class Animal implements IAnimal,Serializable{  
  3.     private String name;  
  4.   
  5.     @Override  
  6.     public String getName() {  
  7.         return name;  
  8.     }  
  9.   
  10.     @Override  
  11.     public void setName(String name) {  
  12.         this.name = name;  
  13.     }  
  14. }  
  15. //給AnimalImpl添加Serializable接口  
  16. public class AnimalImpl extends Animal implements Serializable {  
  17. }  
所以如果我們獲取AnimalImpl類的接口列表,得到的應該是三個:自已直接繼承的Serializable,從父類Animal那繼承的IAnimal和Serializable
好了,言規正轉,看獲取類類型所有接口列表的方法:
[java] view plaincopy
  1. /** 
  2.  * 獲取所傳類類型的所有繼承的接口列表 
  3.  * @param clazz 
  4.  * @return 
  5.  */  
  6. public Class<?>[] getAllInterface(Class<?> clazz){  
  7.   
  8.     //獲取自身的所有接口  
  9.     Class<?>[] interSelf = clazz.getInterfaces();  
  10.     //遞規調用getAllInterface獲取超類的所有接口  
  11.     Class<?> superClazz = clazz.getSuperclass();  
  12.     Class<?>[] interParent = null;  
  13.     if (null != superClazz) {  
  14.         interParent = getAllInterface(superClazz);  
  15.     }  
  16.   
  17.     //返回值  
  18.     if (interParent == null && interSelf != null){  
  19.         return interSelf;  
  20.     }else if (interParent == null && interSelf == null){  
  21.         return null;  
  22.     }else if (interParent != null && interSelf == null){  
  23.         return interParent;  
  24.     }else {  
  25.         final int length = interParent.length + interSelf.length;  
  26.         Class<?>[] result = new Class[length];  
  27.         System.arraycopy(interSelf,0,result,0,interSelf.length);  
  28.         System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
  29.         return result;  
  30.     }  
  31. }  
  32.   
  33.   
  34. //調用  
  35. Class<?>[] clazzes = getAllInterface(AnimalImpl.class);  
  36. SpannableStringBuilder builder = new SpannableStringBuilder();  
  37. for (Class clazz:clazzes){  
  38.     builder.append(clazz.getName());  
  39.     builder.append("   ");  
  40. }  
  41. Log.d(TAG, "AnimalImpl繼承的所有接口:"+ builder.toString());  
先看看執行結果:

這段代碼最關鍵的地方在於getAllInterface(Class<?> clazz);我們來看看這個函數是如何遞規得到所有接口的數組的。

[java] view plaincopy
  1. public Class<?>[] getAllInterface(Class<?> clazz){  
  2.   
  3.     //獲取自身的所有接口  
  4.     Class<?>[] interSelf = clazz.getInterfaces();  
  5.     //遞規調用getAllInterface獲取超類的所有接口  
  6.     Class<?> superClazz = clazz.getSuperclass();  
  7.     Class<?>[] interParent = null;  
  8.     if (null != superClazz) {  
  9.         interParent = getAllInterface(superClazz);  
  10.     }  
  11.   
  12.     //返回值  
  13.     if (interParent == null && interSelf != null){  
  14.         return interSelf;  
  15.     }else if (interParent == null && interSelf == null){  
  16.         return null;  
  17.     }else if (interParent != null && interSelf == null){  
  18.         return interParent;  
  19.     }else {  
  20.         final int length = interParent.length + interSelf.length;  
  21.         Class<?>[] result = new Class[length];  
  22.         System.arraycopy(interSelf,0,result,0,interSelf.length);  
  23.         System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
  24.         return result;  
  25.     }  
  26. }  
這段代碼分爲兩部分,第一部分是獲得自己的接口列表和父類的列表:
[java] view plaincopy
  1. //獲取自身的所有接口  
  2. Class<?>[] interSelf = clazz.getInterfaces();  
  3. //遞規調用getAllInterface獲取超類的所有接口  
  4. Class<?> superClazz = clazz.getSuperclass();  
  5. Class<?>[] interParent = null;  
  6. if (null != superClazz) {  
  7.     interParent = getAllInterface(superClazz);  
  8. }  
首先通過Class<?>[] interSelf = clazz.getInterfaces();獲得自已直接繼承的接口列表。這個很好理解,可能對於有些同學而言,難就難在獲取父類列表的過程:
[java] view plaincopy
  1. Class<?> superClazz = clazz.getSuperclass();  
  2. Class<?>[] interParent = null;  
  3. if (null != superClazz) {  
  4.     interParent = getAllInterface(superClazz);  
  5. }  
在這段代碼中,首先,通過Class<?> superClazz = clazz.getSuperclass();獲取父類的Class類型,然後調用getAllInterface(superClazz)獲得父類的所有接口列表。
有些同學不解了,getAllInterface(superClazz)這不是當前函數自己嗎?是的,我們寫getAllInterface(superClazz)是來幹什麼的,就是用來獲取傳進去的類的所有接口,所以我們把父類傳進去,當然也能獲得它父類的所有接口列表了。(有關遞規的知識,可能是有些難度的,遞規不是本文重點,不做詳細介紹,有疑問的同學可以搜搜這方面文章補充下)
我們再重複一遍,我們的getAllInterface(Class<?> clazz)函數,會返回clazz對象的所有接口列表。現在我們得到了它自己直接繼承的接口,也有它父類的所有接口列表。那麼,把它們兩個合併,就是所有的接口列表了。
所以下面是接口列表返回的代碼:
[java] view plaincopy
  1. if (interParent == null && interSelf != null){  
  2.     return interSelf;  
  3. }else if (interParent == null && interSelf == null){  
  4.     return null;  
  5. }else if (interParent != null && interSelf == null){  
  6.     return interParent;  
  7. }else {  
  8.     final int length = interParent.length + interSelf.length;  
  9.     Class<?>[] result = new Class[length];  
  10.     System.arraycopy(interSelf,0,result,0,interSelf.length);  
  11.     System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
  12.     return result;  
  13. }  
首先,對interParent和interSelf判空,如果兩個列表都是空,那直接返回空;如果有一個是空,另一個不是空,則返回那個不是空的列表,如果兩個都不是空,則將他們合併,然後返回合併後的列表。
有點難度的地方,可能是當兩個都不爲空的時候,合併時的代碼:
[java] view plaincopy
  1. final int length = interParent.length + interSelf.length;  
  2. Class<?>[] result = new Class[length];  
  3. System.arraycopy(interSelf,0,result,0,interSelf.length);  
  4. System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
  5. return result;  
這裏是先根據interParent和interSelf的長度,生成一個它倆總長的一個數組result;
然後通過System.arraycopy()函數,將它們兩個複製到result數組中。
System.arraycopy()的定義及釋義如下:
[java] view plaincopy
  1. /** 
  2.  * 將指定個數的元素從源數組src複製到目標數組dst中 
  3.  * 
  4.  * @param src :源數組 
  5.  * @param srcPos:源數組開始複製的item的索引,從0開始 
  6.  * @param dst:目標數組 
  7.  * @param dstPos:目標數組開始接收復制item的位置索引,從0開始 
  8.  * @param length:要複製的元素個數 
  9.  */  
  10. public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);  
最後就是使用getAllInterface()函數啦:
[java] view plaincopy
  1. Class<?>[] clazzes = getAllInterface(AnimalImpl.class);  
  2. SpannableStringBuilder builder = new SpannableStringBuilder();  
  3. for (Class clazz:clazzes){  
  4.     builder.append(clazz.getName());  
  5.     builder.append("   ");  
  6. }  
  7. Log.d(TAG, "AnimalImpl繼承的所有接口:"+ builder.toString());  
這段代碼很好理解,先獲取AnimalImpl的所有接口列表,然後使用SpannableStringBuilder將它們拼接成一個String字符串,然後打印出來。
到這裏,基本類的周邊信息獲取就結束了,下面我們來看看泛型類的周邊信息獲取要怎麼來做。

(5)、獲取類的訪問修飾符

由於我們在定義類時,比如下面的內部類:
[java] view plaincopy
  1. public static final class InnerClass{  
  2. }  
在類名,前面的那一坨public static final,就是類的訪問修飾符,是定義這個類在的訪問區域和訪問限定的。這部分就講講如何獲取類的這部分訪問修飾符
我們先看一個例子(我們以上面的內部類InnerClass爲例):
[java] view plaincopy
  1. Class<?> clazz = getClassLoader().loadClass(InnerClass.class.getName());  
  2. int modifiers = clazz.getModifiers();  
  3. String retval = Modifier.toString(modifiers);  
  4. boolean isFinal = Modifier.isFinal(modifiers);  
  5. Log.d(TAG, "InnerClass的定義修飾符:" + retval);  
  6. Log.d(TAG, "is Final:" + isFinal);  
結果如下:

首先,在這部分代碼中,我們又換了一種類加載方式,使用的是ClassLoader;
然後我們單獨來看看這句:

[java] view plaincopy
  1. int modifiers = clazz.getModifiers();  
通過clazz.getModifiers()得到一個整型變量,由於訪問修飾符有很多,所以這些修飾符被打包成一個int,對應的二進制中,每個修飾符是一個標誌位,可以被置位或清零。
另外Java開發人員單獨提供了一個類來提取這個整型變量中各標識位的函數,這個類就是Modifier
Modifier中主要有以下幾個方法:
[java] view plaincopy
  1. //根據整型變量來生成對應的修飾符字符串  
  2. String Modifier.toString(int modifiers)   
  3. //以下這些方法來檢查特定的修飾符是否存在  
  4. boolean Modifier.isAbstract(int modifiers)  
  5. boolean Modifier.isFinal(int modifiers)  
  6. boolean Modifier.isInterface(int modifiers)  
  7. boolean Modifier.isNative(int modifiers)  
  8. boolean Modifier.isPrivate(int modifiers)  
  9. boolean Modifier.isProtected(int modifiers)  
  10. boolean Modifier.isPublic(int modifiers)  
  11. boolean Modifier.isStatic(int modifiers)  
  12. boolean Modifier.isStrict(int modifiers)  
  13. boolean Modifier.isSynchronized(int modifiers)  
  14. boolean Modifier.isTransient(int modifiers)  
  15. boolean Modifier.isVolatile(int modifiers)  
首先是toString函數:
[java] view plaincopy
  1. 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 plaincopy
  1. //定義一個類部接口  
  2. public static interface  InnerInteface{  
  3. }  
  4. //使用      
  5. Class<?> clazz2 = InnerInteface.class;  
  6. int modifiers = clazz2.getModifiers();  
  7. String retval = Modifier.toString(modifiers);  
  8. boolean isInteface = Modifier.isInterface(modifiers);  
  9. Log.d(TAG, "InnerClass的定義修飾符:" + retval);  
  10. Log.d(TAG, "isInteface:" + isInteface);  
這裏首先要注意的一點是:
[java] view plaincopy
  1. Class<?> clazz2 = InnerInteface.class;  
如果我們要直接獲取一個接口的對象,同樣,也是通過開頭所講的那四種獲取Class對象的方式。
因爲我們現在知道Class對象,不光代表類也可以代表接口。
下面有關Modifier的使用與第五部分獲取類的修飾符是一樣的,就不再細講。

(7)Class.forName(String className)與ClassLoader.loadClass(String ClassName)的區別

我們通過源碼來看看他們的區別:
先看Class.forName:
[java] view plaincopy
  1. public static Class<?> forName(String className) throws ClassNotFoundException {  
  2.     return forName(className, true, VMStack.getCallingClassLoader());  
  3. }  
  4.   
  5. public static Class<?> forName(String className, boolean initializeBoolean,  
  6.         ClassLoader classLoader) throws ClassNotFoundException {  
  7.   
  8.     Class<?> result;  
  9.     try {  
  10.         result = classForName(className, initializeBoolean,  
  11.                 classLoader);  
  12.     } catch (ClassNotFoundException e) {  
  13.         Throwable cause = e.getCause();  
  14.         if (cause instanceof ExceptionInInitializerError) {  
  15.             throw (ExceptionInInitializerError) cause;  
  16.         }  
  17.         throw e;  
  18.     }  
  19.     return result;  
  20. }  
從源中可以看到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 plaincopy
  1. public Class<?> loadClass(String className) throws ClassNotFoundException {  
  2.     return loadClass(className, false);  
  3. }  
  4. protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {  
  5.     Class<?> clazz = findLoadedClass(className);  
  6.   
  7.     if (clazz == null) {  
  8.         try {  
  9.             clazz = parent.loadClass(className, false);  
  10.         } catch (ClassNotFoundException e) {  
  11.             // Don't want to see this.  
  12.         }  
  13.   
  14.         if (clazz == null) {  
  15.             clazz = findClass(className);  
  16.         }  
  17.     }  
  18.   
  19.     return clazz;  
  20. }  
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 plaincopy
  1. // Register ourselves with the DriverManager  
  2. static {  
  3.     try {  
  4.         java.sql.DriverManager.registerDriver(new Driver());  
  5.     } catch (SQLException E) {  
  6.         throw new RuntimeException("Can't register driver!");  
  7.     }  
  8. }  
原來,Driver在static塊中會註冊自己到java.sql.DriverManager。而static塊就是在Class的初始化中被執行。所以這個地方就只能用Class.forName(className)。

好了,這篇就到這了,內容不太多,但比較複雜。最後我們再總結一下這篇文章所涉及到的幾個函數:
[java] view plaincopy
  1. //獲取類類型對象的幾種方式:  
  2. Person person = new Person();    
  3. Class a = person.getClass() //方法一:  
  4. Class b = Persion.class;//方法二:  
  5. Class c = Class.forName(String ClassName); //方法三:  
  6. Class d = context.getClassLoader().loadClass(String ClassName);//方法四:(不建議使用)  
  7.   
  8. //獲取包名類名  
  9. public String getName();//獲取完整的類名(包含包名)  
  10. public String getSimpleName();//僅獲取類名  
  11. public Package getPackage()//獲取類類型所對應的package對象  
  12.   
  13. //獲取超類Class對象  
  14. public Class<?> getSuperclass();//獲取普通函數的父類Class對象  
  15. public Type getGenericSuperclass();//針對泛型父類而設計(下篇講解)  
  16.   
  17. //獲取接口Class對象  
  18. public Class<?>[] getInterfaces();//獲取普通接口的方法  
  19. public Type[] getGenericInterfaces();//獲取泛型接口的方法  
  20.   
  21. //類訪問修飾符  
  22. int modifiers = clazz.getModifiers();//獲取類訪問修飾符對應的int變量  
  23. String Modifier.toString(int modifiers) //根據整型變量來生成對應的修飾符字符串  
  24. boolean Modifier.isAbstract(int modifiers)//isXXX()系列函數用以檢查特定的修飾符是否存在  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章