反射概述
1 什麼是反射
讓我們從Class類開始瞭解反射!
每個加載到方法區中的class文件都對應一個Class類的對象,你可以把Class類的對象理解爲硬盤上的class文件的對應體。
2 反射的作用
反射是Java中的高級特性,在各種Java框架中都需要使用反射。所以,就算你將來很長一段時間不使用反射,但你使用的框架都大量使用了反射,所以想深入學習框架,那麼就一定要學習反射。
框架通常通過反射來識別一個對象的“類型信息”。當你傳遞給框架一個對象時,框架會通過反射來了解對象的真實類型(對象實體的類型,而不是引用的類型),這個類型有幾個構造器,有什麼樣的屬性,有什麼樣的方法。還可以通過反射調用構造器,調用方法,對屬性進行讀寫操作。
你可能覺得這沒有什麼神奇的,那是你還沒了解我說的是什麼!你需要再想一想,寫一個方法,參數是Object obj,然後你的方法需要創建一個與參數類型相同的對象出來,還要調用這個對象上的方法。需要注意,參數是Object類型,但用戶調用這個方法時,可能傳遞的不是Object實體對象,它的真實類型有可能是任何類型。
public static void fun(Object obj) {
通過反射創建obj相同類型的對象;
調用obj的方法,調用的方法可以是obj真實類型獨有的方法。而不一定非要Object中的方法。
}
3 Class類都有什麼功能
通過面向對象的思想,猜測一個類中應該有什麼樣的方法了。
一個Student類用來表示學生類型,學生應該有名字,那麼學生類就應該有getName()方法。學生也應該有學號,那麼學生類就應該有getNumber()方法…
一個Class類用來表示類類型,類應該有屬性,那麼Class類就應該有getField()方法,但一個類可以有多個屬性,那麼Class類有的就不是getField(),而是getFields()方法,返回值爲Field[]類型。相同的道理,Class類也應該有getMethods()和getConstructors()方法,用來返回這個類的所有方法和所有構造器的定義。其實今後我們去學習,很可能就只是想使用一個類的一個方法!然後纔去查找這個類,再去學習我們需要的方法,對於這個類的其他就一無所知了!你很可能在編寫某個項目時會有這樣的想法:某某某方法應該是某某某類的!!!然後你就去查找API!
描述學生的類型、描述老師的類型、描述計算機的類型,都是很好理解的,但描述類的類型總是讓人感覺怪怪的,這也是初學者需要習慣的地方。還有,我們在學習過程中稱呼這些類型時也需要注意一下,例如Field表示屬性類,Field類有一個屬性叫name,那麼我們需要怎麼說明這個name屬性呢?“屬性類的name屬性”,這是一個不錯的稱呼!
Class:類的反射對象;
Field:屬性反射對象;
Method:方法反射對象;
Constructor:構造器反射對象;
Class、Field、Method、Construcator:統稱爲“反射對象”。
Class類
1 反射從Class類開始
要想使用反射,首先你需要得到Class對象,然後才能通過Class對象獲取Constructor、Field、Method等對象。所有的反射對象都不可能自己來new,說白一點,這些反射對象對應的是class文件上的信息,你怎麼可能自己去new呢?如果可以自己去new一個Class類的對象,那麼是不是就不用我們再去編寫.java文件,然後再通過編譯器去編譯成.class文件了呢?當然這是不可能的!
我們需要思考,Class除了可以返回當前對應類型的所有屬性、方法、構造器的反射對象外,還有什麼功能呢?例如對應類型的類名是什麼?對應類型的父類是誰?對應類型是不是public類,是不是final類。對應類型有沒有可能是個數組類型?有沒有可能是接口類型?有沒有可能是基本類型等等!如果你學會了這樣思考,那麼你今後學習新類是就方便多了!
2 得到Class對象
通過對象獲取Class對象:obj.getClass();
你很清楚要使用的Class類型:String.class、int.class,只需要在類型的後面添加.class就表示一個Class類型的實例了。
通過類名來獲取Class對象:在只有一個字符串(類名)時使用Class.forName(String className)來獲取Class對象。
Class c1 = “”.getClass(); Class c2 = String.class; Class c3 = Class.forName(“java.lang.String”); System.out.println(c1 == c2); System.out.println(c2 == c3); |
上面代碼輸出的都是true,這是因爲一個.class文件,在方法區中只對應一個Class對象。
3 加載類
我們已經知道,main()方法是程序的入口。那是不是在main()方法開始執行之前,所有的class文件都已經加載到方法區中了呢?答案是:NO!通常只有需要執行到使用某個類的代碼時,纔會去CLASSPATH中加載class文件,如果程序從頭到尾都沒有使用某個類,那麼這個類對應的class文件就不會被加載到內存。
可以導致一個類被加載可能有:
l 使用一個類的靜態方法;
l 使用一個類的靜態屬性;
l 創建這個類的對象;
l 使用Class.forName()方法加載類;
l 反序列化一個類的對象;
l 加載一個類的子類時,也會加載其父類;
l 加載一個類時,也會加載與該類相關的類。
上面給出的幾個可能也只是可能而已,如果當前類沒有被加載過,纔會去加載,如果已經加載到方法區中了,那麼就不可能再去加載。
4.Class類方法
方法1:
l String getName():返回類名;
l String getSimpleName():返回簡單類名,不包含包名,但數組類型使用它比較方便;
l Class getSuperClass():獲取父類,Object.class.getSupperClass()返回null;
l int getModifiers():獲取類的所有修飾符信息;
方法2:
l Constructor getConstructor(Class… parameterTypes):通過指定的參數類型獲取公有構造器反射對象;
l Constructor[] getConstructors():獲取所有公有構造器對象;
l Constructor getDeclaredConstructor(Class… parameterTypes):通過指定參數類型獲取構造器反射對象。包含私有構造器對象;
l Constructor[] getDeclaredConstructors():獲取所有構造器對象。包含私有構造器;
l Field getField(String name):通過名字獲取公有屬性反射對象,包含父類中聲明的公有屬性;
l Field[] getFields():獲取所有公有屬性反射對象,包含父類中聲明的公有屬性;
l Field getDeclaredField(String name):通過名字獲取本類中某個屬性,包含本類的private屬性,但父類中聲明的任何屬性都不包含;
l Field[] getDeclaredFields():獲取本類中聲明的所有屬性,包含private屬性,但不包含父類中聲明的任何屬性;
l Method getMethod(String name, Class… parameterTypes):通過方法名和方法參數類型獲取方法反射對象,包含父類中聲明的公有方法,但不包含所有私有方法;
l Method[] getMethods():獲取所有公有方法,包含父類中的公有方法,但不包含任何私有方法;
l Method getDeclaredMethod(String name, Class… parameterTypes):通過方法名和方法參數類型獲取本類中聲明的方法的反射對象,包含本類中的私有方法,但不包含父類中的任何方法;
l Method[] getDeclaredMethods():獲取本類中所有方法,包含本類中的私有方法,但不包含父類中的任何方法。
方法3:
l boolean isArray():是否爲數組類型;
l boolean isAnnotation():是否爲註解類型;
l boolean isAnnotationPresent(Class annotationClass):當前類是否被annotationClass註解了;
l boolean isEnum():是否爲枚舉類型;
l boolean isInterface():是否爲接口類型;
l boolean isPrimitive():是否爲基本類型;
l boolean isSynthetic():是否爲引用類型;
方法4:
l T newInstance():使用本類無參構造器來創建本類對象;
其他反射類
其他反射類都在java.lang.reflect包下
1.AccessibleObject
AccessibleObject類是Constructor、Method、Field三個類的父類。
l Annotation getAnnotation(Class annotationClass):獲取作用在當前成員上的annotationClass類型的註解對象;
l Annotation[] getAnnotations():獲取作用在當前成員上的所有註解對象;
l boolean isAccessible():判斷當前成員是否可訪問;
l void setAccessible(boolean flag):設置當前成員是否可訪問。
2.Construcator
String getName():獲取構造器名;
int getModifiers():獲取構造器上的所有修飾符信息;
Class getDeclaringClass():獲取構造器所屬的類型;
Class[] getParameterTypes():獲取構造器的所有參數的類型;
Class[] getExceptionTypes():獲取構造器上聲明的所有異常類型;
T newInstance(Object… initargs):通過構造器反射對象調用構造器。
3.Method
l String getName():獲取方法名;
l int getModifiers():獲取方法上的所有修飾符信息;
l Class getDeclaringClass():獲取方法所屬的類型;
l Class[] getParameterTypes():獲取方法的所有參數的類型;
l Class[] getExceptionTypes():獲取方法上聲明的所有異常類型;
l Class getReturnType():獲取方法的返回值類型;
l Object invode(Object obj, Object… args):通過方法反射對象調用方法,如果當前方法是實例方法,那麼當前對象就是obj,如果當前方法是static方法,那麼可以給obj傳遞null。args表示是方法的參數;
4.Field
l String getName():獲取屬性名;
l int getModifiers():獲取屬性上的所有修飾符信息;
l Class getDeclaringClass():獲取屬性所屬的類型;
l Class getType():獲取當前屬性的類型;
l Object get(Object obj):獲取obj對象的當前屬性值;
l void set(Object obj, Object value):設置obj對象的當前屬性值爲value;
l XXX getXXX(Object obj):如果當前屬性爲基本類型,可以使用getXXX()系列方法獲取基本類型屬性值。假如當前屬性爲int類型,那麼可以使用getInt(Object obj)方法獲取obj對象的當前屬性值;
l void setXXX(Object obj, XXX value):如果當前屬性爲基本類型,可以使用setXXX()系統方法基本類型屬性值。假如當前屬性爲int類型,那麼可以使用setInt(Object obj, int value)方法設置obj對象的當前屬性值爲value。
5.Modifier
Modifier類有一系列的static方法用來解析其他getModifiers()方法返回的int值。
Method m = … int m = m.getModifiers(); boolean b1 = Modifier.isAbstract(m);//解析m中是否包含abstract修飾 boolean b2 = Modifier.isStatic(m);//解析m中是否包含static修飾 String s = Modifiers.toString(m);//把所有修飾都轉換成字符串 |