本文部分內容參考部落格。點選連結可以檢視原文。
1. 反射的概念
反射是指在執行時將類的屬性、建構函式和方法等元素動態地對映成一個個物件。通過這些物件我們可以動態地生成物件例項,呼叫類的方法和更改類的屬性值。
2. 使用場景
什麼情況下運用JAVA反射呢?如果編譯時根本無法預知物件和類可能屬於哪些類,程式只依靠執行時資訊來發現該物件和類的真實資訊,此時就必須使用反射。
使用反射可以實現下面的功能:
- 在執行時判斷任意一個物件所屬的類
- 在執行時構造任意一個類的物件
- 在執行時判斷任意一個類所具有的方法和屬性
- 在執行時呼叫任意一個物件的方法
- 生成動態代理
3. 獲得Class物件的幾種方式
前面已經介紹過了,每個類被載入之後,系統就會為該類生成一個對應的Class物件,通過該Class物件就可以訪問到JVM中的這個類。在Java程式中獲得Class物件通常有如下3種方式。
- 使用Class類的forName(String clazzName)靜態方法。該方法需要傳入字串引數,該字串引數的值是某個類的全限定類名(必須新增完整包名)。
- 呼叫某個類的class屬性來獲取該類對應的Class物件。例如,Person.class將會返回Person類對應的Class物件。
- 呼叫某個物件的getClass()方法。該方法是java.lang.Object類中的一個方法,所以所有的Java物件都可以呼叫該方法,該方法將會返回該物件所屬類對應的Class物件。
對於第一種方式和第二種方式都是直接根據類來取得該類的Class物件,相比之下,第二種方式有如下兩種優勢。
- 程式碼更安全。程式在編譯階段就可以檢查需要訪問的Class物件是否存在。
- 程式效能更好。因為這種方式無須呼叫方法,所以效能更好。
也就是說,大部分時候我們都應該使用第二種方式來獲取指定類的Class物件。但如果我們只有一個字串,例如“java.lang.String”,若需要獲取該字串對應的Class物件,則只能使用第一種方式,使用Class的forName(String clazzName)方法獲取Class物件時,該方法可能丟擲一個ClassNotFoundException異常。一旦獲得了某個類所對應的Class物件之後,程式就可以呼叫Class物件的方法來獲得該物件和該類的真實資訊了。
4. Class類 API介紹
通過class類我們能夠獲取大量的資訊:
- 獲取建構函式
- Connstructor
getConstructor(Class<?>... parameterTypes):返回此Class物件對應類的指定public構造器。 - Constructor<?>[] getConstructors():返回此Class物件對應類的所有public構造器。
- Constructor
getDeclaredConstructor(Class<?>... parameterTypes):返回此Class物件對應類的指定構造器,與構造器的訪問許可權無關。 - Constructor<?>[] getDeclaredConstructors():返回此Class物件對應類的所有構造器,與構造器的訪問許可權無關。
- 獲取方法
- Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回此Class物件對應類的指定方法,與方法的訪問許可權無關。
- Method[] getDeclaredMethods():返回此Class物件對應類的全部方法,與方法的訪問許可權無關。
- 獲取屬性
- Field getField(String name):返回此Class物件對應類的指定public Field。
- Field[] getFields():返回此Class物件對應類的所有public Field。
- Field getDeclaredField(String name):返回此Class物件對應類的指定Field,與Field的訪問許可權無關。
- Field[] getDeclaredFields():返回此Class物件對應類的全部Field,與Field的訪問許可權無關。
- 獲取Class對應類上所包含的Annotation。
- A getAnnotation(Class annotationClass):試圖獲取該Class物件對應類上指定型別的Annotation;如果該型別的註釋不存在,則返回null。
- Annotation[] getAnnotations():返回該Class物件對應類上的所有Annotation。
- Annotation[] getDeclaredAnnotations():返回直接修飾該Class對應類的所有Annotation。
- 獲取Class物件對應類包含的內部類。
- Class<?>[] getDeclaredClasses():返回該Class物件對應類裡包含的全部內部類。
如下方法用於訪問該Class物件對應類所在的外部類。 - Class<?> getDeclaringClass():返回該Class物件對應類所在的外部類。
如下方法用於訪問該Class物件對應類所繼承的父類、所實現的介面等。 - Class<?>[] getInterfaces():返回該Class物件對應類所實現的全部介面。
- 獲取Class物件對應類所繼承的父類
- Class<? super T> getSuperclass():返回該Class物件對應類的超類的Class物件。
- 獲取Class物件對應類的修飾符、所在包、類名等基本資訊。
- int getModifiers():返回此類或介面的所有修飾符。修飾符由public、protected、private、final、static、abstract等對應的常量組成,返回的整數應使用Modifier工具類的方法來解碼,才可以獲取真實的修飾符。
- Package getPackage():獲取此類的包。
- String getName():以字串形式返回此Class物件所表示的類的名稱。
- String getSimpleName():以字串形式返回此Class物件所表示的類的簡稱。
- 判斷該類是否為介面、列舉、註釋型別等
- boolean isAnnotation():返回此Class物件是否表示一個註釋型別(由@interface定義)。
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判斷此Class物件是否使用了Annotation註釋修飾。
- boolean isAnonymousClass():返回此Class物件是否是一個匿名類。
- boolean isArray():返回此Class物件是否表示一個陣列類。
- boolean isEnum():返回此Class物件是否表示一個列舉(由enum關鍵字定義)。
- boolean isInterface():返回此Class物件是否表示一個介面(使用interface定義)。
- boolean isInstance(Object obj):判斷obj是否是此Class物件的例項,該方法可以完全代替instanceof操作符。
上面的多個getMethod()方法和getConstructor()方法中,都需要傳入多個型別為Class<?>的引數,用於獲取指定的方法或指定的構造器。關於這個引數的作用,假設某個類內包含如下3個info方法簽名:
- public void info()
- public void info(String str)
- public void info(String str , Integer num)
這3個同名方法屬於過載,它們的方法名相同,但引數列表不同。在Java語言中要確定一個方法光有方法名是不行的,例如,我們指定info方法——實際上可以是上面3個方法中的任意一個!如果需要確定一個方法,則應該由方法名和形參列表來確定,但形參名沒有任何實際意義,所以只能由形參型別來確定。例如,我們想要確定第二個info方法,則必須指定方法名為info,形參列表為String.class——因此在程式中獲取該方法使用如下程式碼:
clazz.getMethod("info",String.class);
使用反射生成物件
- 利用建構函式生成物件
- 使用Class物件的newInstance()方法來建立該Class物件對應類的例項,這種方式要求該Class物件的對應類有預設構造器,而執行newInstance()方法時實際上是利用預設構造器來建立該類的例項。
- 先使用Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立該Class物件對應類的例項。通過這種方式可以選擇使用指定的構造器來建立例項。
Constructor c = clazz.getConstructor(String.class);
c.newInstance("xx");
呼叫方法
Method setProName = aClass.getDeclaredMethod("setProName",String.class);
setProName.setAccessible(true);
etProName.invoke(product,"我是一個產品");
操作屬性
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("fieldName:"+declaredField.getName()+" filedType:"+declaredField.getType());
}
Field proName = aClass.getDeclaredField("proName");
proName.setAccessible(true);
proName.set(product,"我是一個產品");
System.out.println("修稿屬性:"+product);
運算元組
//使用反射動態地建立陣列
//建立一個元素型別為String,長度為3的陣列
Object arr = Array.newInstance(String.class, 3);
//依次為arr陣列中index為0,1,2的元素賦值
Array.set(arr, 0, "榮耀盒子");
Array.set(arr, 1, "榮耀8手機");
Array.set(arr, 2, "華為mate9保時捷版");
Object o1= Array.get(arr, 0);
Object o2= Array.get(arr, 1);
Object o3= Array.get(arr, 2);
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);
5. 使用Demo
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class<Product> aClass = Product.class;
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.getName());
}
Constructor<Product> constructor = aClass.getConstructor(int.class, String.class);
Product product = constructor.newInstance(10, "ds");
System.out.println("建立物件:"+product);
//獲取方法並呼叫
Method setProName = aClass.getDeclaredMethod("setProName", String.class);
setProName.setAccessible(true);
setProName.invoke(product,"我是一個產品");
System.out.println("呼叫方法:"+product);
//獲取屬性,並設定屬性的值
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("fieldName:"+declaredField.getName()+" filedType:"+declaredField.getType());
}
Field proName = aClass.getDeclaredField("proName");
proName.setAccessible(true);
proName.set(product,"我是一個產品");
System.out.println("修稿屬性:"+product);
//使用反射動態地建立陣列
//建立一個元素型別為String,長度為3的陣列
Object arr = Array.newInstance(String.class, 3);
//依次為arr陣列中index為0,1,2的元素賦值
Array.set(arr, 0, "榮耀盒子");
Array.set(arr, 1, "榮耀8手機");
Array.set(arr, 2, "華為mate9保時捷版");
Object o1= Array.get(arr, 0);
Object o2= Array.get(arr, 1);
Object o3= Array.get(arr, 2);
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);
System.out.println("end...");
}
}