天天看點

Java 反射(1):基本類周邊資訊擷取

一、引入

在開始反射之前,我們先看看JVM是如何将我們寫的類對應的java檔案加載到記憶體中的。

1、類的生命周期

這部分我們先講講JVM的加載機制(可能會有點難度,我盡力講的直白點)

我們寫一個最簡單的Main函數,來看看這個函數的是如何被執行的,代碼如下:(Main.java)

[java]  view plain copy

  1. public class Main {  
  2.     public static void main(String[] args)  {  
  3.         Animal animal = new Animal();  
  4.         animal.setName("cat");  
  5.     }  
  6.     public static class Animal{  
  7.         private String name;  
  8.         public String getName() {  
  9.             return name;  
  10.         }  
  11.         public void setName(String name) {  
  12.             this.name = name;  
  13.         }  
  14.     }  
  15. }  

這段代碼很簡單,我們定義了一個Animal的類,在main()函數中,我們首先定義了一個Animal執行個體,然後調用了該執行個體的setName()方法。

大家都知道,在拿到一個java源檔案後,如果要經過源碼編譯,要經過兩個階段:

編譯:

[java]  view plain copy

  1. javac Main.java  

在執行後後在同一目錄下生成Main.class和Animal類對應的檔案Main$Animal.class(由于我們的Animal類是Main中的内部類,是以用$表示Main類中的内部類)

運作

然後使用java Main指令運作程式:

[java]  view plain copy

  1. java Main  

在這一階段,又分為三個小階段:裝載,連結,初始化

裝載: 類的裝載是通過類加載器完成的,加載器将.class檔案的二進制檔案裝入JVM的方法區,并且在堆區建立描述這個類的java.lang.Class對象。用來封裝資料。 但是同一個類隻會被類裝載器裝載一次,記住:隻裝載一次!

連結: 連結就是把二進制資料組裝為可以運作的狀态。連結分為校驗,準備,解析這3個階段。校驗一般用來确認此二進制檔案是否适合目前的JVM(版本),準備就是為靜态成員配置設定記憶體空間,并設定預設值。解析指的是轉換常量池中的代碼作為直接引用的過程,直到所有的符号引用都可以被運作程式使用(建立完整的對應關系)。

初始化: 初始化就是對類中的變量進行初始化值;完成之後,類型也就完成了初始化,初始化之後類的對象就可以正常使用了,直到一個對象不再使用之後,将被垃圾回收。釋放空間。

當沒有任何引用指向Class對象時就會被解除安裝,結束類的生命周期。如果再次用到就再重新開始裝載、連結和初始化的過程。

上面這一大段有關類生命周期有講解,可能會有些難度,畢竟有關JVM的東東不是三言兩語能講透徹的,通過上面的這一段隻想告訴大家一點: 類隻會被裝載一次!!!!利用裝載的類可以執行個體化出各種不同的對象!

2、擷取類類型

1、泛型隐藏填充類型預設填充為無界通配符?

在上面,我們講了,類隻會被裝載一次,利用裝載的類可以執行個體化出各種不同的對象。而反射就是通過擷取裝載的類來做出各種操作的。裝載的類,我們稱為類類型,利用裝載的類産生的執行個體,我們稱為類執行個體。下面我們就看看,如何利用代碼擷取類類型的:

[java]  view plain copy

  1. //使用方法一  
  2. Class class1 = Animal.class;  
  3. System.out.println(class1.getName());  
  4. //使用方法二  
  5. Class<?> class2= Animal.class;  
  6. System.out.println(class2.getName());  

運作結果如下:

Java 反射(1):基本類周邊資訊擷取

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

[java]  view plain copy

  1. //使用方法一  
  2. Class class1 = Animal.class;  
  3. //使用方法二  
  4. Class<?> class2= Animal.class;  

可以看到這兩個方法,右邊全部都是Animal.class,而左邊卻有些不同。

方法一中,是直接生成了一個Class的執行個體。

而在方法二中,則生成的是一個Class的泛型,并且使用的是無界通配符來填充的。有關無界通配符的事,下面再說,這裡先講講方法一中直接生成Class對象與方法二中生成的Class泛型的差別。

我們都知道,Class類是一個泛型。而泛型的正規寫法就應該是

[java]  view plain copy

  1. Class<Animal> class2= Animal.class;  

而方法一,隻是把泛型的填充為省略了,在泛型中,如果把泛型的填充給省略掉,那就會預設填充為無界通配符?。是以方法一的真實寫法是這樣的:

[java]  view plain copy

  1. Class<?> class1 = Animal.class;  

是以這兩種寫法是意義是完全相同的。

如果我們不用通配符,也就隻能這樣寫:

[java]  view plain copy

  1. Class<Animal> class2= Animal.class;  

有關泛型和通配符的用法請參看: 《夯實JAVA基本之一 —— 泛型詳解(1):基本使用》 (上面這部分,在泛型詳解中也講過)

在本文中,為了不誤導大家,我們采用完整的Class填充方式即Class<?>

2、擷取類類型的方法

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

[java]  view plain copy

  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 plain copy

  1. boolean result = (clazz1 == clazz2 && clazz3 == clazz4 && clazz1 == clazz3);  

result的值為true;

下面我們針對方法三和方法四舉個粟子來看下:

先看一下完整的代碼結構:

Java 反射(1):基本類周邊資訊擷取

可以看到我們有一個Activity:MyActivity,和一個類Animal;

我們在Activity上放一個btn,把所有代碼放在btn的點選響應中,Activity的布局難度不大就不再貼出代碼,下面僅針對類類型的代碼講解。

Animal類的定義與上方一樣,唯一不同的是,Animal類已經不再是内部類了而是單獨出來的一個類。

[java]  view plain copy

  1. public class Animal {  
  2.     private String name;  
  3.     public String getName() {  
  4.         return name;  
  5.     }  
  6.     public void setName(String name) {  
  7.         this.name = name;  
  8.     }  
  9. }  

然後在Activity上btn點選時:

[java]  view plain copy

  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 plain copy

  1. public void demoFunc()throws Exception{  
  2.     Class<?> class1 = Class.forName("com.example.myReflect.Animal");  
  3.     Log.d(TAG,"通過Class.forName獲得的類名:"+class1.getName());  
  4.     class1 = getClassLoader().loadClass("com.example.myReflect.Animal");  
  5.     Log.d(TAG,"通過ClassLoader獲得的類名:"+class1.getName());  
  6. }  

其中

[java]  view plain copy

  1. private static String TAG = "qijian";  

結果如下:

Java 反射(1):基本類周邊資訊擷取

從上面的用法中,可以看出,我們要使用Class.forName()或者getClassLoader().loadClass(),其中的類名必須是從包名到類名的完整路徑!

從這裡看來Class.forName()和getClassLoader().loadClass()是相同的,其實他們是有差別的,平時,我們不建議使用getClassLoader().loadClass()的方法來加載類類型。有關Class.forName()和getClassLoader().loadClass()的具體差別,會在本篇末尾講述。

二、基本類類型周邊資訊擷取

我們知道類分為基本類和泛型類,這篇我們隻講基本類類型的周邊資訊擷取,有關泛型類的周邊資訊擷取,我們會放到下一篇中。

這部分主要講述類類型周邊資訊擷取方法,包括類名,包名,超類和繼承接口。

1、類名,包名擷取

相關的有三個函數:

[java]  view plain copy

  1. //擷取完整的類名(包含包名)  
  2. public String getName();  
  3. //僅擷取類名  
  4. public String getSimpleName();  
  5. //擷取類類型所對應的package對象  
  6. public Package getPackage()  

上面都有解釋,我們用一下這幾個函數:

[java]  view plain copy

  1. Class<?> class1 = Animal.class;  
  2. Package package1 = class1.getPackage();  
  3. Log.d(TAG,"完整的類名:"+class1.getName());  
  4. Log.d(TAG,"僅擷取類名:"+class1.getSimpleName());  
  5. Log.d(TAG,"包名:"+package1.getName());  
Java 反射(1):基本類周邊資訊擷取

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

有關Package類的相關函數就不講了,基本用不到。

(2)、擷取超類Class對象

擷取superClass的類對象,涉及到兩個函數:

[java]  view plain copy

  1. //擷取普通函數的父類Class對象  
  2. public Class<?> getSuperclass();  
  3. //針對泛型父類而設計  
  4. public Type getGenericSuperclass();  

getSuperclass()用來擷取普通函數,而getGenericSuperclass()是用來擷取泛型類型的父類而設計的,有關getGenericSuperclass()的知識我們後面會講,這裡先看看getSuperclass()的用法。

我們仍然利用前面講到的Animal類,然後在其上派生一個AnimalImpl子類:

[java]  view plain copy

  1. public class Animal {  
  2.     private String name;  
  3.     public String getName() {  
  4.         return name;  
  5.     }  
  6.     public void setName(String name) {  
  7.         this.name = name;  
  8.     }  
  9. }  
  10. public class AnimalImpl extends Animal {  
  11. }  

然後使用:

[java]  view plain copy

  1. Class<?> class2 = Class.forName("com.example.myReflect.AnimalImpl");  
  2. Class<?> parentClass = class2.getSuperclass();  
  3. Log.d(TAG, "父類:" + parentClass.getName());  

結果如下:

Java 反射(1):基本類周邊資訊擷取

在這裡,我們使用了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

  1. //擷取普通接口的方法  
  2. public Class<?>[] getInterfaces();  
  3. //擷取泛型接口的方法  
  4. public Type[] getGenericInterfaces();  

與擷取superClass對象一樣,這裡同樣有兩個函數來擷取接口對象,有關getGenericInterfaces()擷取泛型接口的方法,我們下篇再講,這裡先講講擷取普通接口的方法getInterfaces();

getInterfaces()将擷取指定類直接繼承的接口清單!注意一點:直接繼承!!!如果不是直接繼承,那将是擷取不到的。

我們舉個例子:

同樣,以上面的Animal為例:

我們先聲明一個接口,讓Animal類來繼承:

[java]  view plain copy

  1. public interface IAnimal {  
  2.     void setName(String name);  
  3.     String getName();  
  4. }  

然後是Animal類繼承接口:

[java]  view plain copy

  1. public class Animal implements IAnimal{  
  2.     private String name;  
  3.     @Override  
  4.     public String getName() {  
  5.         return name;  
  6.     }  
  7.     @Override  
  8.     public void setName(String name) {  
  9.         this.name = name;  
  10.     }  
  11. }  

為了測試不是直接繼承的接口是無法擷取的問題,我們再從Animal派生一個子類AnimalImpl:

[java]  view plain copy

  1. public class AnimalImpl extends Animal {  
  2. }  

我們再整理一下思路,Animal類直接繼承了IAnimal,而AnimalImpl僅僅派生自Animal,它的IAnimal接口不是直接繼承的,而是從它的父類Aniaml那帶過來的

然後我們分别看看Animal類和AnimalImpl類的的擷取接口的結果,完整的代碼如下:

[java]  view plain copy

  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. //擷取AnimalImpl的接口清單  
  8. class3 = AnimalImpl.class;  
  9. interfaces = class3.getInterfaces();  
  10. if (interfaces.length >0) {  
  11.     for (Class interItem : interfaces) {  
  12.         Log.d(TAG, "AnimalImpl繼承的接口:" + interItem.getName());  
  13.     }  
  14. }else {  
  15.     Log.d(TAG, "AnimalImpl無繼承的接口");  
  16. }  

結果如下:

Java 反射(1):基本類周邊資訊擷取

我們先看看Animal類的接口清單:

[java]  view plain copy

  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 plain copy

  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 plain copy

  1. //給Animal添加 IAnimal,Serializable兩個接口  
  2. public class Animal implements IAnimal,Serializable{  
  3.     private String name;  
  4.     @Override  
  5.     public String getName() {  
  6.         return name;  
  7.     }  
  8.     @Override  
  9.     public void setName(String name) {  
  10.         this.name = name;  
  11.     }  
  12. }  
  13. //給AnimalImpl添加Serializable接口  
  14. public class AnimalImpl extends Animal implements Serializable {  
  15. }  

是以如果我們擷取AnimalImpl類的接口清單,得到的應該是三個:自已直接繼承的Serializable,從父類Animal那繼承的IAnimal和Serializable

好了,言規正轉,看擷取類類型所有接口清單的方法:

[java]  view plain copy

  1. public Class<?>[] getAllInterface(Class<?> clazz){  
  2.     //擷取自身的所有接口  
  3.     Class<?>[] interSelf = clazz.getInterfaces();  
  4.     //遞規調用getAllInterface擷取超類的所有接口  
  5.     Class<?> superClazz = clazz.getSuperclass();  
  6.     Class<?>[] interParent = null;  
  7.     if (null != superClazz) {  
  8.         interParent = getAllInterface(superClazz);  
  9.     }  
  10.     //傳回值  
  11.     if (interParent == null && interSelf != null){  
  12.         return interSelf;  
  13.     }else if (interParent == null && interSelf == null){  
  14.         return null;  
  15.     }else if (interParent != null && interSelf == null){  
  16.         return interParent;  
  17.     }else {  
  18.         final int length = interParent.length + interSelf.length;  
  19.         Class<?>[] result = new Class[length];  
  20.         System.arraycopy(interSelf,0,result,0,interSelf.length);  
  21.         System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
  22.         return result;  
  23.     }  
  24. }  
  25. //調用  
  26. Class<?>[] clazzes = getAllInterface(AnimalImpl.class);  
  27. SpannableStringBuilder builder = new SpannableStringBuilder();  
  28. for (Class clazz:clazzes){  
  29.     builder.append(clazz.getName());  
  30.     builder.append("   ");  
  31. }  
  32. Log.d(TAG, "AnimalImpl繼承的所有接口:"+ builder.toString());  

先看看執行結果:

Java 反射(1):基本類周邊資訊擷取

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

[java]  view plain copy

  1. public Class<?>[] getAllInterface(Class<?> clazz){  
  2.     //擷取自身的所有接口  
  3.     Class<?>[] interSelf = clazz.getInterfaces();  
  4.     //遞規調用getAllInterface擷取超類的所有接口  
  5.     Class<?> superClazz = clazz.getSuperclass();  
  6.     Class<?>[] interParent = null;  
  7.     if (null != superClazz) {  
  8.         interParent = getAllInterface(superClazz);  
  9.     }  
  10.     //傳回值  
  11.     if (interParent == null && interSelf != null){  
  12.         return interSelf;  
  13.     }else if (interParent == null && interSelf == null){  
  14.         return null;  
  15.     }else if (interParent != null && interSelf == null){  
  16.         return interParent;  
  17.     }else {  
  18.         final int length = interParent.length + interSelf.length;  
  19.         Class<?>[] result = new Class[length];  
  20.         System.arraycopy(interSelf,0,result,0,interSelf.length);  
  21.         System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
  22.         return result;  
  23.     }  
  24. }  

這段代碼分為兩部分,第一部分是獲得自己的接口清單和父類的清單:

[java]  view plain copy

  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 plain copy

  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 plain copy

  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 plain copy

  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 plain copy

  1. public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);  

最後就是使用getAllInterface()函數啦:

[java]  view plain copy

  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 plain copy

  1. public static final class InnerClass{  
  2. }  

在類名,前面的那一坨public static final,就是類的通路修飾符,是定義這個類在的通路區域和通路限定的。這部分就講講如何擷取類的這部分通路修飾符

我們先看一個例子(我們以上面的内部類InnerClass為例):

[java]  view plain copy

  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);  

結果如下:

Java 反射(1):基本類周邊資訊擷取

首先,在這部分代碼中,我們又換了一種類加載方式,使用的是ClassLoader;

然後我們單獨來看看這句:

[java]  view plain copy

  1. int modifiers = clazz.getModifiers();  

通過clazz.getModifiers()得到一個整型變量,由于通路修飾符有很多,是以這些修飾符被打包成一個int,對應的二進制中,每個修飾符是一個标志位,可以被置位或清零。

另外Java開發人員單獨提供了一個類來提取這個整型變量中各辨別位的函數,這個類就是Modifier

Modifier中主要有以下幾個方法:

[java]  view plain copy

  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 plain copy

  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 plain copy

  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 plain copy

  1. Class<?> clazz2 = InnerInteface.class;  

如果我們要直接擷取一個接口的對象,同樣,也是通過開頭所講的那四種擷取Class對象的方式。

因為我們現在知道Class對象,不光代表類也可以代表接口。

下面有關Modifier的使用與第五部分擷取類的修飾符是一樣的,就不再細講。

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

我們通過源碼來看看他們的差別:

先看Class.forName:

[java]  view plain copy

  1. public static Class<?> forName(String className) throws ClassNotFoundException {  
  2.     return forName(className, true, VMStack.getCallingClassLoader());  
  3. }  
  4. public static Class<?> forName(String className, boolean initializeBoolean,  
  5.         ClassLoader classLoader) throws ClassNotFoundException {  
  6.     Class<?> result;  
  7.     try {  
  8.         result = classForName(className, initializeBoolean,  
  9.                 classLoader);  
  10.     } catch (ClassNotFoundException e) {  
  11.         Throwable cause = e.getCause();  
  12.         if (cause instanceof ExceptionInInitializerError) {  
  13.             throw (ExceptionInInitializerError) cause;  
  14.         }  
  15.         throw e;  
  16.     }  
  17.     return result;  
  18. }  

從源中可以看到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

  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.     if (clazz == null) {  
  7.         try {  
  8.             clazz = parent.loadClass(className, false);  
  9.         } catch (ClassNotFoundException e) {  
  10.             // Don't want to see this.  
  11.         }  
  12.         if (clazz == null) {  
  13.             clazz = findClass(className);  
  14.         }  
  15.     }  
  16.     return clazz;  
  17. }  

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

  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 plain copy

  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. public String getName();//擷取完整的類名(包含包名)  
  9. public String getSimpleName();//僅擷取類名  
  10. public Package getPackage()//擷取類類型所對應的package對象  
  11. //擷取超類Class對象  
  12. public Class<?> getSuperclass();//擷取普通函數的父類Class對象  
  13. public Type getGenericSuperclass();//針對泛型父類而設計(下篇講解)  
  14. //擷取接口Class對象  
  15. public Class<?>[] getInterfaces();//擷取普通接口的方法  
  16. public Type[] getGenericInterfaces();//擷取泛型接口的方法  
  17. //類通路修飾符  
  18. int modifiers = clazz.getModifiers();//擷取類通路修飾符對應的int變量  
  19. String Modifier.toString(int modifiers) //根據整型變量來生成對應的修飾符字元串  
  20. boolean Modifier.isAbstract(int modifiers)//isXXX()系列函數用以檢查特定的修飾符是否存在