一、引入
在開始反射之前,我們先看看JVM是如何將我們寫的類對應的java文件加載到內存中的。1、類的生命週期
這部分我們先講講JVM的加載機制(可能會有點難度,我盡力講的直白點)我們寫一個最簡單的Main函數,來看看這個函數的是如何被執行的,代碼如下:(Main.java)
- 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;
- }
- }
- }
大家都知道,在拿到一個java源文件後,如果要經過源碼編譯,要經過兩個階段:
編譯:
- javac Main.java
運行
然後使用java Main命令運行程序:
- java Main
裝載:類的裝載是通過類加載器完成的,加載器將.class文件的二進制文件裝入JVM的方法區,並且在堆區創建描述這個類的java.lang.Class對象。用來封裝數據。 但是同一個類只會被類裝載器裝載一次,記住:只裝載一次!
鏈接:鏈接就是把二進制數據組裝爲可以運行的狀態。鏈接分爲校驗,準備,解析這3個階段。校驗一般用來確認此二進制文件是否適合當前的JVM(版本),準備就是爲靜態成員分配內存空間,並設置默認值。解析指的是轉換常量池中的代碼作爲直接引用的過程,直到所有的符號引用都可以被運行程序使用(建立完整的對應關係)。
初始化:初始化就是對類中的變量進行初始化值;完成之後,類型也就完成了初始化,初始化之後類的對象就可以正常使用了,直到一個對象不再使用之後,將被垃圾回收。釋放空間。
當沒有任何引用指向Class對象時就會被卸載,結束類的生命週期。如果再次用到就再重新開始裝載、鏈接和初始化的過程。
上面這一大段有關類生命週期有講解,可能會有些難度,畢竟有關JVM的東東不是三言兩語能講透徹的,通過上面的這一段只想告訴大家一點:類只會被裝載一次!!!!利用裝載的類可以實例化出各種不同的對象!
2、獲取類類型
1、泛型隱藏填充類型默認填充爲無界通配符?
在上面,我們講了,類只會被裝載一次,利用裝載的類可以實例化出各種不同的對象。而反射就是通過獲取裝載的類來做出各種操作的。裝載的類,我們稱爲類類型,利用裝載的類產生的實例,我們稱爲類實例。下面我們就看看,如何利用代碼獲取類類型的:
- //使用方法一
- Class class1 = Animal.class;
- System.out.println(class1.getName());
- //使用方法二
- Class<?> class2= Animal.class;
- System.out.println(class2.getName());
從結果中可以看出class1和class2是完全一樣的,那構造他們時的方法一和方法二有什麼區別呢?
- //使用方法一
- Class class1 = Animal.class;
- //使用方法二
- Class<?> class2= Animal.class;
方法一中,是直接生成了一個Class的實例。
而在方法二中,則生成的是一個Class的泛型,並且使用的是無界通配符來填充的。有關無界通配符的事,下面再說,這裏先講講方法一中直接生成Class對象與方法二中生成的Class泛型的區別。
我們都知道,Class類是一個泛型。而泛型的正規寫法就應該是
- Class<Animal> class2= Animal.class;
- Class<?> class1 = Animal.class;
如果我們不用通配符,也就只能這樣寫:
- Class<Animal> class2= Animal.class;
在本文中,爲了不誤導大家,我們採用完整的Class填充方式即Class<?>
2、獲取類類型的方法
上面我們通過Class<?> class1 = Animal.class,即直接使用類名的Class對象可以獲取類類型,這只是其中一個方法,下面這四種方法都可以獲得對應的類類型:
- //方法一:
- 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);
方法二中,直接通過類的class對象得到
方法三和方法四中是通過類名得到,這兩點要非常注意,這裏的ClassName一定要從包名具體到類名,唯一定位到一個類才行,不然就會報ClassNotFound錯誤
在上面我們提到過,類只會被加載一次,所以a,b,c,d都是相等的,因爲他們都是指向同一個對象,如果用等號操作符來判斷的話:
- boolean result = (clazz1 == clazz2 && clazz3 == clazz4 && clazz1 == clazz3);
下面我們針對方法三和方法四舉個粟子來看下:
先看一下完整的代碼結構:
可以看到我們有一個Activity:MyActivity,和一個類Animal;
我們在Activity上放一個btn,把所有代碼放在btn的點擊響應中,Activity的佈局難度不大就不再貼出代碼,下面僅針對類類型的代碼講解。
Animal類的定義與上方一樣,唯一不同的是,Animal類已經不再是內部類了而是單獨出來的一個類。
- public class Animal {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- //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());
- }
- }
- });
- 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());
- }
- private static String TAG = "qijian";
從上面的用法中,可以看出,我們要使用Class.forName()或者getClassLoader().loadClass(),其中的類名必須是從包名到類名的完整路徑!
從這裏看來Class.forName()和getClassLoader().loadClass()是相同的,其實他們是有區別的,平時,我們不建議使用getClassLoader().loadClass()的方法來加載類類型。有關Class.forName()和getClassLoader().loadClass()的具體區別,會在本篇末尾講述。
二、基本類類型周邊信息獲取
我們知道類分爲基本類和泛型類,這篇我們只講基本類類型的周邊信息獲取,有關泛型類的周邊信息獲取,我們會放到下一篇中。
這部分主要講述類類型周邊信息獲取方法,包括類名,包名,超類和繼承接口。
1、類名,包名獲取
相關的有三個函數:- //獲取完整的類名(包含包名)
- public String getName();
- //僅獲取類名
- public String getSimpleName();
- //獲取類類型所對應的package對象
- public Package getPackage()
- 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的類對象,涉及到兩個函數:- //獲取普通函數的父類Class對象
- public Class<?> getSuperclass();
- //針對泛型父類而設計
- public Type getGenericSuperclass();
我們仍然利用前面講到的Animal類,然後在其上派生一個AnimalImpl子類:
- public class Animal {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- public class AnimalImpl extends Animal {
- }
- 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對象,它可能表示的類對象,也可能表示的是接口對象!
獲取接口對象的函數如下:
- //獲取普通接口的方法
- public Class<?>[] getInterfaces();
- //獲取泛型接口的方法
- public Type[] getGenericInterfaces();
getInterfaces()將獲取指定類直接繼承的接口列表!注意一點:直接繼承!!!如果不是直接繼承,那將是獲取不到的。
我們舉個例子:
同樣,以上面的Animal爲例:
我們先聲明一個接口,讓Animal類來繼承:
- public interface IAnimal {
- void setName(String name);
- String getName();
- }
- public class Animal implements IAnimal{
- private String name;
- @Override
- public String getName() {
- return name;
- }
- @Override
- public void setName(String name) {
- this.name = name;
- }
- }
- public class AnimalImpl extends Animal {
- }
然後我們分別看看Animal類和AnimalImpl類的的獲取接口的結果,完整的代碼如下:
- //獲取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類的接口列表:
- Class<?> class3 = Animal.class;
- Class<?>[] interfaces = class3.getInterfaces();
- for (Class interItem:interfaces){
- Log.d(TAG, "Animal繼承的接口:" + interItem.getName());
- }
從結果可以看出,這裏找到了Animal類所繼承的接口值。
那我們再來看看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無繼承的接口");
- }
從結果也可以看出,這裏獲取到的接口列表爲空!所以這也證明了getInterfaces()只能獲取類直接繼承的接口列表。
(4)、綜合提升:獲取某個類類型的所有接口
現在我們提升一下,如果我想傳進去一下類類型,然後要得到它所有繼承的接口列表要怎麼辦?(不管它是不是直接繼承來的都要列出來)那只有靠遞規了,我們需要遞規它的父類直接繼承的接口、父類的父類直接繼承的接口以此類推,最終到Object類的時候就找到所有繼承的接口了
在開始遞規獲取所有接口之前,我們先構造下代碼。
由於我們要獲取所有接口,爲了效果更好些,我們在Animal和AnimalImpl基礎上,多加幾個繼承的接口:
- //給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 {
- }
好了,言規正轉,看獲取類類型所有接口列表的方法:
- /**
- * 獲取所傳類類型的所有繼承的接口列表
- * @param clazz
- * @return
- */
- 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);我們來看看這個函數是如何遞規得到所有接口的數組的。
- 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<?>[] interSelf = clazz.getInterfaces();
- //遞規調用getAllInterface獲取超類的所有接口
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
- Class<?> superClazz = clazz.getSuperclass();
- Class<?>[] interParent = null;
- if (null != superClazz) {
- interParent = getAllInterface(superClazz);
- }
有些同學不解了,getAllInterface(superClazz)這不是當前函數自己嗎?是的,我們寫getAllInterface(superClazz)是來幹什麼的,就是用來獲取傳進去的類的所有接口,所以我們把父類傳進去,當然也能獲得它父類的所有接口列表了。(有關遞規的知識,可能是有些難度的,遞規不是本文重點,不做詳細介紹,有疑問的同學可以搜搜這方面文章補充下)
我們再重複一遍,我們的getAllInterface(Class<?> clazz)函數,會返回clazz對象的所有接口列表。現在我們得到了它自己直接繼承的接口,也有它父類的所有接口列表。那麼,把它們兩個合併,就是所有的接口列表了。
所以下面是接口列表返回的代碼:
- 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;
- }
有點難度的地方,可能是當兩個都不爲空的時候,合併時的代碼:
- 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;
然後通過System.arraycopy()函數,將它們兩個複製到result數組中。
System.arraycopy()的定義及釋義如下:
- /**
- * 將指定個數的元素從源數組src複製到目標數組dst中
- *
- * @param src :源數組
- * @param srcPos:源數組開始複製的item的索引,從0開始
- * @param dst:目標數組
- * @param dstPos:目標數組開始接收復制item的位置索引,從0開始
- * @param length:要複製的元素個數
- */
- public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);
- 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());
到這裏,基本類的周邊信息獲取就結束了,下面我們來看看泛型類的周邊信息獲取要怎麼來做。
(5)、獲取類的訪問修飾符
由於我們在定義類時,比如下面的內部類:- public static final class InnerClass{
- }
我們先看一個例子(我們以上面的內部類InnerClass爲例):
- 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;
然後我們單獨來看看這句:
- int modifiers = clazz.getModifiers();
另外Java開發人員單獨提供了一個類來提取這個整型變量中各標識位的函數,這個類就是Modifier
Modifier中主要有以下幾個方法:
- //根據整型變量來生成對應的修飾符字符串
- 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)
- String Modifier.toString(int modifiers)
其它的就是一些isXXXX(int moifiers)的判斷指定標識位的函數了,沒什麼難度
在例子中,我們使用了Modifier.isFinal(int modifiers)來判斷是不是具有final修飾符,返回結果爲true;
從Modifier類的isXXX()系列函數中,可以看到它的修飾符確實很多,但這些修飾符,有些類是根本用不到的,比如isNative()等,這是因爲不光類會使用Modifier類來判斷訪問修飾符,接口,成員變量和成員函數所對應的類型也同樣都是使用Modifier來判斷訪問修飾符的,這些我們後面在講到的時候,都會說到!
(6)、獲取接口的訪問修飾符
從上面獲取類的訪問修飾符時,我們講過,接口,類,函數都是通過Modifier類判斷訪問修飾符的,又因爲類和接口類型全部都是用Class對象來標識,所以接口和類的獲取訪問修飾符的方式完全相同,下面就舉一個簡單的例子:- //定義一個類部接口
- 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);
- Class<?> clazz2 = InnerInteface.class;
因爲我們現在知道Class對象,不光代表類也可以代表接口。
下面有關Modifier的使用與第五部分獲取類的修飾符是一樣的,就不再細講。
(7)Class.forName(String className)與ClassLoader.loadClass(String ClassName)的區別
我們通過源碼來看看他們的區別:先看Class.forName:
- 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;
- }
其中:
- className:類名
- initializeBoolean:表示是否需要初始化;如果設爲true,表示在加載以後,還會進入鏈接階段
- classLoader:ClassLoader加載器
下面再來看看ClassLoader.loadClass()
- 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;
- }
通過代碼也可以看出來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的源代碼看看,
- // Register ourselves with the DriverManager
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- }
好了,這篇就到這了,內容不太多,但比較複雜。最後我們再總結一下這篇文章所涉及到的幾個函數:
- //獲取類類型對象的幾種方式:
- 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()系列函數用以檢查特定的修飾符是否存在