1. 程式人生 > >java基礎-反射(1):基本類周邊資訊獲取

java基礎-反射(1):基本類周邊資訊獲取

前言:堅持夢想,過程或是艱辛的,回憶是幸福的。與其最後豪言如果當時我怎樣怎樣,倒不如堅持腳下。

相關文章:

今天開始給大家講講有關反射的知識,在應用程式開發時,如果純做上層,搭搭框架啥的,那用到反射的機會不多,但如果你想做出來一個公共類或者公共模組給其它人用的時候,那用到反射的可能性就大大增加了。況且反射聽起來也是蠻屌的名字,今天就我們徹底認識他下吧。

一、引入

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

1、類的生命週期

這部分我們先講講JVM的載入機制(可能會有點難度,我盡力講的直白點)
我們寫一個最簡單的Main函式,來看看這個函式的是如何被執行的,程式碼如下:(Main.java)
  1. public
    class Main {  
  2.     publicstaticvoid main(String[] args)  {  
  3.         Animal animal = new Animal();  
  4.         animal.setName("cat");  
  5.     }  
  6.     publicstaticclass Animal{  
  7.         private String name;  
  8.         public String getName() {  
  9.             return name;  
  10.         }  
  11.         publicvoid setName(String name) {  
  12.             this.name = name;  
  13.         }  
  14.     }  
  15. }  
這段程式碼很簡單,我們定義了一個Animal的類,在main()函式中,我們首先定義了一個Animal例項,然後呼叫了該例項的setName()方法。
大家都知道,在拿到一個java原始檔後,如果要經過原始碼編譯,要經過兩個階段:
編譯:
  1. javac Main.java  
在執行後後在同一目錄下生成Main.class和Animal類對應的檔案Main$Animal.class(由於我們的Animal類是Main中的內部類,所以用$表示Main類中的內部類)
執行
然後使用java Main命令執行程式:
  1. java Main  
在這一階段,又分為三個小階段:裝載,連結,初始化

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

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

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

2、獲取類型別

1、泛型隱藏填充型別預設填充為無界萬用字元?

在上面,我們講了,類只會被裝載一次,利用裝載的類可以例項化出各種不同的物件。而反射就是通過獲取裝載的類來做出各種操作的。裝載的類,我們稱為類型別,利用裝載的類產生的例項,我們稱為類例項。下面我們就看看,如何利用程式碼獲取類型別的:
  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是完全一樣的,那構造他們時的方法一和方法二有什麼區別呢?

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

2、獲取類型別的方法

上面我們通過Class<?> class1 = Animal.class,即直接使用類名的Class物件可以獲取類型別,這只是其中一個方法,下面這四種方法都可以獲得對應的類型別:
  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都是相等的,因為他們都是指向同一個物件,如果用等號操作符來判斷的話:
  1. boolean result = (clazz1 == clazz2 && clazz3 == clazz4 && clazz1 == clazz3);  
result的值為true;
下面我們針對方法三和方法四舉個粟子來看下:
先看一下完整的程式碼結構:

可以看到我們有一個Activity:MyActivity,和一個類Animal;
我們在Activity上放一個btn,把所有程式碼放在btn的點選響應中,Activity的佈局難度不大就不再貼出程式碼,下面僅針對類型別的程式碼講解。
Animal類的定義與上方一樣,唯一不同的是,Animal類已經不再是內部類了而是單獨出來的一個類。
  1. publicclass Animal {  
  2.     private String name;  
  3.     public String getName() {  
  4.         return name;  
  5.     }  
  6.     publicvoid setName(String name) {  
  7.         this.name = name;  
  8.     }  
  9. }  
然後在Activity上btn點選時:
  1. //btn點選時呼叫demoFunc()函式
  2. Button button = (Button)findViewById(R.id.btn);  
  3. button.setOnClickListener(new View.OnClickListener() {  
  4.     @Override
  5.     publicvoid onClick(View v) {  
  6.         try{  
  7.             demoFunc();  
  8.         }catch (Exception e){  
  9.             System.out.print(e.getMessage());  
  10.         }  
  11.     }  
  12. });  
demoFunc()函式程式碼如下:
  1. publicvoid 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. }  
其中
  1. privatestatic String TAG = "qijian";  
結果如下:

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

二、基本類型別周邊資訊獲取


我們知道類分為基本類和泛型類,這篇我們只講基本類型別的周邊資訊獲取,有關泛型類的周邊資訊獲取,我們會放到下一篇中。
這部分主要講述類型別周邊資訊獲取方法,包括類名,包名,超類和繼承介面。

1、類名,包名獲取

相關的有三個函式:
  1. //獲取完整的類名(包含包名)
  2. public String getName();  
  3. //僅獲取類名
  4. public String getSimpleName();  
  5. //獲取類型別所對應的package物件
  6. public Package getPackage()  
上面都有解釋,我們用一下這幾個函式:
  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());  

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

(2)、獲取超類Class物件

獲取superClass的類物件,涉及到兩個函式:
  1. //獲取普通函式的父類Class物件
  2. public Class<?> getSuperclass();  
  3. //針對泛型父類而設計
  4. public Type getGenericSuperclass();  
getSuperclass()用來獲取普通函式,而getGenericSuperclass()是用來獲取泛型型別的父類而設計的,有關getGenericSuperclass()的知識我們後面會講,這裡先看看getSuperclass()的用法。
我們仍然利用前面講到的Animal類,然後在其上派生一個AnimalImpl子類:
  1. publicclass Animal {  
  2.     private String name;  
  3.     public String getName() {  
  4.         return name;  
  5.     }  
  6.     publicvoid setName(String name) {  
  7.         this.name = name;  
  8.     }  
  9. }  
  10. publicclass AnimalImpl extends Animal {  
  11. }  
然後使用:
  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物件,它可能表示的類物件,也可能表示的是介面物件!
獲取介面物件的函式如下:
  1. //獲取普通介面的方法
  2. public Class<?>[] getInterfaces();  
  3. //獲取泛型介面的方法
  4. public Type[] getGenericInterfaces();  
與獲取superClass物件一樣,這裡同樣有兩個函式來獲取介面物件,有關getGenericInterfaces()獲取泛型介面的方法,我們下篇再講,這裡先講講獲取普通介面的方法getInterfaces();
getInterfaces()將獲取指定類直接繼承的介面列表!注意一點:直接繼承!!!如果不是直接繼承,那將是獲取不到的。
我們舉個例子:
同樣,以上面的Animal為例:
我們先宣告一個介面,讓Animal類來繼承:
  1. publicinterface IAnimal {  
  2.     void setName(String name);  
  3.     String getName();  
  4. }  
然後是Animal類繼承介面:
  1. publicclass Animal implements IAnimal{  
  2.     private String name;  
  3.     @Override
  4.     public String getName() {  
  5.         return name;  
  6.     }  
  7.     @Override
  8.     publicvoid setName(String name) {  
  9.         this.name = name;  
  10.     }  
  11. }  
為了測試不是直接繼承的介面是無法獲取的問題,我們再從Animal派生一個子類AnimalImpl:
  1. publicclass AnimalImpl extends Animal {  
  2. }  
我們再整理一下思路,Animal類直接繼承了IAnimal,而AnimalImpl僅僅派生自Animal,它的IAnimal介面不是直接繼承的,而是從它的父類Aniaml那帶過來的
然後我們分別看看Animal類和AnimalImpl類的的獲取介面的結果,完整的程式碼如下:
  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. }  
結果如下:

我們先看看Animal類的介面列表:

  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的介面獲取情況又是怎樣呢:
  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基礎上,多加幾個繼承的介面:
  1. //給Animal新增 IAnimal,Serializable兩個介面
  2. publicclass Animal implements IAnimal,Serializable{  
  3.     private String name;  
  4.     @Override
  5.     public String getName() {  
  6.         return name;  
  7.     }  
  8.     @Override
  9.     publicvoid setName(String name) {  
  10.         this.name = name;  
  11.     }  
  12. }  
  13. //給AnimalImpl新增Serializable介面
  14. publicclass AnimalImpl extends Animal implements Serializable {  
  15. }  
所以如果我們獲取AnimalImpl類的介面列表,得到的應該是三個:自已直接繼承的Serializable,從父類Animal那繼承的IAnimal和Serializable
好了,言規正轉,看獲取類型別所有介面列表的方法:
  1. /** 
  2.  * 獲取所傳類型別的所有繼承的介面列表 
  3.  * @param clazz 
  4.  * @return 
  5.  */
  6. public Class<?>[] getAllInterface(Class<?> clazz){  
  7.     //獲取自身的所有介面
  8.     Class<?>[] interSelf = clazz.getInterfaces();  
  9.     //遞規呼叫getAllInterface獲取超類的所有介面
  10.     Class<?> superClazz = clazz.getSuperclass();  
  11.     Class<?>[] interParent = null;  
  12.     if (null != superClazz) {  
  13.         interParent = getAllInterface(superClazz);  
  14.     }  
  15.     //返回值
  16.     if (interParent == null && interSelf != null){  
  17.         return interSelf;  
  18.     }elseif (interParent == null && interSelf == null){  
  19.         returnnull;  
  20.     }elseif (interParent != null && interSelf == null){  
  21.         return interParent;  
  22.     }else {  
  23.         finalint length = interParent.length + interSelf.length;  
  24.         Class<?>[] result = new Class[length];  
  25.         System.arraycopy(interSelf,0,result,0,interSelf.length);  
  26.         System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
  27.         return result;  
  28.     }  
  29. }  
  30. //呼叫
  31. Class<?>[] clazzes = getAllInterface(AnimalImpl.class);  
  32. SpannableStringBuilder builder = new SpannableStringBuilder();  
  33. for (Class clazz:clazzes){  
  34.     builder.append(clazz.getName());  
  35.     builder.append("   ");  
  36. }  
  37. Log.d(TAG, "AnimalImpl繼承的所有介面:"+ builder.toString());  
先看看執行結果:

這段程式碼最關鍵的地方在於getAllInterface(Class<?> clazz);我們來看看這個函式是如何遞規得到所有介面的陣列的。

  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.     }elseif (interParent == null && interSelf == null){  
  14.         returnnull;  
  15.     }elseif (interParent != null && interSelf == null){  
  16.         return interParent;  
  17.     }else {  
  18.         finalint 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. }  
這段程式碼分為兩部分,第一部分是獲得自己的介面列表和父類的列表:
  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();獲得自已直接繼承的介面列表。這個很好理解,可能對於有些同學而言,難就難在獲取父類列表的過程:
  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物件的所有介面列表。現在我們得到了它自己直接繼承的介面,也有它父類的所有介面列表。那麼,把它們兩個合併,就是所有的介面列表了。
所以下面是介面列表返回的程式碼:
  1. if (interParent == null && interSelf != null){  
  2.     return interSelf;  
  3. }elseif (interParent == null && interSelf == null){  
  4.     returnnull;  
  5. }elseif (interParent != null && interSelf == null){  
  6.     return interParent;  
  7. }else {  
  8.     finalint 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判空,如果兩個列表都是空,那直接返回空;如果有一個是空,另一個不是空,則返回那個不是空的列表,如果兩個都不是空,則將他們合併,然後返回合併後的列表。
有點難度的地方,可能是當兩個都不為空的時候,合併時的程式碼:
  1. finalint 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()的定義及釋義如下:
  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. publicstaticnativevoid arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);  
最後就是使用getAllInterface()函式啦:
  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)、獲取類的訪問修飾符

由於我們在定義類時,比如下面的內部類:
  1. publicstaticfinalclass InnerClass{  
  2. }  
在類名,前面的那一坨public static final,就是類的訪問修飾符,是定義這個類在的訪問區域和訪問限定的。這部分就講講如何獲取類的這部分訪問修飾符
我們先看一個例子(我們以上面的內部類InnerClass為例):
  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;
然後我們單獨來看看這句:

  1. int modifiers = clazz.getModifiers();  
通過clazz.getModifiers()得到一個整型變數,由於訪問修飾符有很多,所以這些修飾符被打包成一個int,對應的二進位制中,每個修飾符是一個標誌位,可以被置位或清零。
另外Java開發人員單獨提供了一個類來提取這個整型變數中各標識位的函式,這個類就是Modifier
Modifier中主要有以下幾個方法:
  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函式:
  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物件來標識,所以介面和類的獲取訪問修飾符的方式完全相同,下面就舉一個簡單的例子:
  1. //定義一個類部介面
  2. publicstaticinterface  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);  
這裡首先要注意的一點是:
  1. Class<?> clazz2 = InnerInteface.class;  
如果我們要直接獲取一個介面的物件,同樣,也是通過開頭所講的那四種獲取Class物件的方式。
因為我們現在知道Class物件,不光代表類也可以代表介面。
下面有關Modifier的使用與第五部分獲取類的修飾符是一樣的,就不再細講。

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

我們通過原始碼來看看他們的區別:
先看Class.forName:
  1. publicstatic Class<?> forName(String className) throws ClassNotFoundException {  
  2.     return forName(className, true, VMStack.getCallingClassLoader());  
  3. }  
  4. publicstatic 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()
  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的原始碼看看,
  1. // Register ourselves with the DriverManager
  2. static {  
  3.     try {  
  4.         java.sql.DriverManager.registerDriver(new Driver());  
  5.     } catch (SQLException E) {  
  6.         thrownew RuntimeException("Can't register driver!");  
  7.     }  
  8. }  
原來,Driver在static塊中會註冊自己到java.sql.DriverManager。而static塊就是在Class的初始化中被執行。所以這個地方就只能用Class.forName(className)。

好了,這篇就到這了,內容不太多,但比較複雜。最後我們再總結一下這篇文章所涉及到的幾個函式:
  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. 相關推薦

    java基礎-反射(1)基本周邊資訊獲取

    前言:堅持夢想,過程或是艱辛的,回憶是幸福的。與其最後豪言如果當時我怎樣怎樣,倒不如堅持腳下。 相關文章: 今天開始給大家講講有關反射的知識,在應用程式開發時,如果純做上層,搭搭框架啥的,那用到反射的機會不多,但如果你想做出來一個公共類或者公共模組給其它人用的時候,那用到反射的可能性就大大增加了。況且

    java基礎(一) 深入解析基本

    後者 active 位數 自動完成 符號 情況 換算 ade 相等 一、基本類型的簡介 基本類型的兩條準則: Java中,如果對整數不指定類型,默認時int類型,對小數不指定類型,默認是double類型。 基本類型由小到大,可以自動轉換,但是由大到小,則需要強制類型轉換。

    java基礎(一) 深入解析基本

    java一、基本類型的簡介 基本類型的兩條準則: Java中,如果對整數不指定類型,默認時int類型,對小數不指定類型,默認是double類型。 基本類型由小到大,可以自動轉換,但是由大到小,則需要強制類型轉換。 所占的字節數: byte: 1個字節;char: 2個字節;short: 2個字節;int:

    java基礎之十三Abstract和方法

    .get 引用 ava ESS 實現 print student 通過 bst 這篇介紹Java中抽象類和抽象方法,用關鍵字abstract表示抽象,是一個可以修飾類和方法的關鍵字。如果類名前面用abstract修飾,這個類就是抽象類。如果方法名稱前面有abstract修

    spring基礎1基本概念)

    poj 操作 共享問題 元素 組成 The 開發 let 可選值 本系列筆記來自對《Spring實戰》第三版的整理,Spring版本為3.0 ??spring是為了解決企業級應用開發的復雜性而創建的,spring最根本的使命是:簡化Java開發。為降低開發復雜性有以下四種關

    java基礎梳理二基本資料型別、變數

    1、基本資料型別  分為四大類: 佔用位元組數①整數型別byte:位元組型別 1short:短整型 2int:整型 4long:長整型 8②浮點數型別float:單精度                

    java基礎梳理三基本資料型別轉換、運算子

    1、基本資料型別轉換 byte i = 2;int j = 3;byte result = i + j;×①賦值號右側兩個int型別的變數相加,得到的還是一個int型別的結果,把int型別的結果賦值給byte型別的變數,產生精度丟失,提示出錯 ②賦值號右側int型別的變數和byte型別的變數相加

    Java自學之路-Java基礎教程-1第一行Java程式碼Hello World!

    Java是一門很熱門的計算機語言,它能為網際網路應用、企業內部應用提供很好的程式,還可以在硬體如手機,家電,機床,伺服器,電腦,機器人等上面進行嵌入式程式設計讓硬體活動,也可以用來建設網站,比如影音藝這個網站就是使用純Java語言寫的。這是由於Java具有的幾大特性:

    Java基礎系列1Java面向物件

    該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接著瞭解每個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,形成自己的知識框架。   概述: Java是面向物件的程式設計語言,Java語言提供了定義類、成員變數、方法等最基本的功能。類可被認為是一種自

    Java基礎系列4抽象與介面的前世今生

    該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接著瞭解每個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,形成自己的知識框架。   1、抽象類:   當編寫一個類時,常常會為該類定義一些方法,這些方法用以描述該類的行為方式,那麼這些方法都

    Java基礎_3.5簡單Java

    inf 簡單 字符串 stat 被調用 name屬性 職位 void 類的定義 簡單Java類 簡單Java類是一種在實際開發之中使用最多的類的定義形式,在簡單Java類中包含有類、對象、構造方法、private封裝等核心概念的使用,而對於簡單Java類首先給出如下的基本開

    Java基礎 實驗二和物件

    1.實驗目的 掌握類的宣告、物件的建立、方法的定義和呼叫、建構函式的使用。 2.實驗內容 1)定義一個表示學生資訊的類Student,要求如下: ① 類Student的成員變數:       sNO 表示學號;      

    Java學習筆記1計算機基礎知識、java語言基礎

    一、計算機基礎知識 1、 計算機是一種能夠按照程式執行,自動、高速處理海量資料的現代化智慧電子裝置。由硬體和軟體所組成,沒有安裝任何軟體的計算機稱為裸機。常見的形式有臺式計算機、筆記本計算機、大型計算機等。 硬體通常由CPU、主機板、記憶體、電源、主機箱、硬碟、顯示卡、鍵盤、滑鼠,顯示器等多

    Java基礎知識回顧之一 ----- 基本數據

    博客 string 它的 基本數據 int short 溢出 etc tostring 前言 在開始工作至今,學習各種各樣的技術之中發現自己的很多Java的基礎知識都忘了⊙﹏⊙b汗。。。 而且越是學習越是發現Java基礎的重要性,所以準備單獨抽一下時間進行Java基礎的重新

    java基礎知識1:關鍵字;介面和抽象;java併發相關

    true、false、null都不是關鍵字 goto、const、是保留的關鍵字 abstract continue for new switch defa

    學習Spring必學的Java基礎知識(1)----反射

    是什麼?   1.Java語言允許通過程式化的方式間接對Class進行操作,Class檔案由類裝載器裝載後,在JVM中將形成一份描述Class結構的元資訊物件,通過該元資訊物件可以獲知Class的結構資訊:如建構函式、屬性和方法等。Java允許使用者藉由這個Class相關的

    Java併發指南1併發基礎Java多執行緒

    什麼是併發 在過去單CPU時代,單任務在一個時間點只能執行單一程式。之後發展到多工階段,計算機能在同一時間點並行執行多工或多程序。雖然並不是真正意義上的“同一時間點”,而是多個任務或程序共享一個CPU,並交由作業系統來完成多工間對CPU的執行切換,以使得每個任務都有機會

    java基礎-反射詳解與反射是否會破壞的封裝性見解

    問題:反射是否會破壞類的封裝性見解       首先,封裝,是將具體的實現細節隱藏,而把功能作為整體提供給類的外部使用,也就是說,公有方法能夠完成類所具有的功能。當別人使用這個類時,如果通過反射直接呼叫私有方法,可能根本實現不了類的功能,甚至可能會出錯,因此通過反射呼叫私有方

    Java基礎--反射機制簡單使用(Class

    //插槽類 public interface InterfaceDemo { public void open(); public void run(); public void close(); } //手機主機板類 public class CardSlotDem

    番外 01Spring IoC 實現原理簡析,Java反射機制,通過名建立物件

    轉載請註明來源 賴賴的部落格 前景概要 在 01 走進Spring,Context、Bean和IoC 中,我們看到了強大的Spring通過ApplicationContext實現了bean工廠(也就是物件工廠),那究竟是怎麼實現的呢,本次給大家寫一個小D