能夠分析類能力的程式稱為反射(reflective)。反射機制的功能非常強大,主要提供瞭如下功能:
-
對於任意一個類,都能夠知道這個類的所有屬性和方法;
-
對於任意一個物件,都能夠呼叫它的任意方法和屬性;
Class類
在程式執行期間,Java執行時系統始終為所有的物件維護一個被稱為執行時的型別標識。這個資訊跟蹤著每個物件所屬的類。虛擬機器利用執行時型別資訊選擇相應的方法執行。然而,可以通過專門的Java類訪問這些資訊,這個類為java.lang.Class。
類Class的例項表示正在執行的Java應用程式中的類和介面。 列舉是一種類,一個註釋是一種介面。每個陣列也屬於一個類,它被反映為具有相同元素型別和維數的所有陣列共享的Class物件。 原始Java型別(boolean,byte,char,short,int,long,float和double)和關鍵字void也表示為Class物件。
Class類沒有公共建構函式。Class物件由Java虛擬機器自動構建,因為載入了類,並且通過呼叫類載入器中的defineClass方法。
雖然我們不能new一個Class物件,但是卻可以通過已有的類得到一個Class物件,共有如下三種方式:
1、Class類的forName方法
Class<?> clazz = Class.forName("com.codersm.study.jdk.reflect.Person");
複製程式碼
2、通過一個類的物件的getClass()方法
Class<?> clazz = new Person().getClass();
複製程式碼
3、Class字面常量
Class<Person> clazz = Person.Class;
複製程式碼
小結:
注意呼叫forName方法時需要捕獲一個名稱為ClassNotFoundException的異常,因為forName方法在編譯器是無法檢測到其傳遞的字串對應的類是否存在的,只能在程式執行時進行檢查,如果不存在就會丟擲ClassNotFoundException異常。
Class字面常量這種方法更加簡單,更安全。因為它在編譯器就會受到編譯器的檢查同時由於無需呼叫forName方法效率也會更高,因為通過字面量的方法獲取Class物件的引用不會自動初始化該類?。 更加有趣的是字面常量的獲取Class物件引用方式不僅可以應用於普通的類,也可以應用用介面,陣列以及基本資料型別。
Class類提供了大量的例項方法來獲取該Class物件所對應的詳細資訊,Class類大致包含如下方法,其中每個方法都包含多個過載版本,因此我們只是做簡單的介紹,詳細請參考JDK文件。
-
獲取類資訊
獲取內容 方法簽名 構造器 Constructor<T> getConstructor(Class<?>... parameterTypes)
方法 Method getMethod(String name, Class<?>... parameterTypes)
屬性 Field getField(String name)
Annotation <A extends Annotation> A getAnnotation(Class<A> annotationClass)
內部類 Class<?>[] getDeclaredClasses()
外部類 Class<?> getDeclaringClass()
實現的介面 Class<?>[] getInterfaces()
修飾符 int getModifiers()
所在包 Package getPackage()
類名 String getName()
簡稱 String getSimpleName()
注: getDeclaredXxx方法可以獲取所有的Xxx,無論private/public。
-
判斷類本身資訊的方法
判斷內容 方法簽名 註解型別? boolean isAnnotation()
使用了該Annotation修飾? boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
匿名類? boolean isAnonymousClass()
陣列? boolean isArray()
列舉? boolean isEnum()
原始型別? boolean isPrimitive()
介面? boolean isInterface()
obj是否是該Class的例項 boolean isInstance(Object obj)
-
使用反射生成並操作對
-
程式可以通過Method物件來執行相應的方法;
-
通過Constructor物件來呼叫對應的構造器建立例項;
-
通過Filed物件直接訪問和修改物件的成員變數值。
-
建立物件
通過反射來生成物件的方式有兩種:
-
使用Class物件的newInstance()方法來建立該Class物件對應類的例項(這種方式要求該Class物件的對應類有預設構造器)。
-
先使用Class物件獲取指定的Constructor物件, 再呼叫Constructor物件的newInstance()方法來建立該Class物件對應類的例項(通過這種方式可以選擇指定的構造器來建立例項)。
class Person {
private String name;
private Integer age;
public Person() {
this.name = "system";
this.age = 99;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) throws Exception {
Class<Person> pClass = Person.class;
// 通過第1種方式建立物件
Person p = pClass.newInstance();
System.out.println(p);
// 通過第2種方式建立物件
Constructor<Person> constructor = pClass.getDeclaredConstructor(
String.class, Integer.class);
Person person2 = constructor.newInstance("zhangsan",20);
System.out.println(person2);
}
}
複製程式碼
呼叫方法
當獲取到某個類對應的Class物件之後, 就可以通過該Class物件的getMethod來獲取一個Method陣列或Method物件.每個Method物件對應一個方法,在獲得Method物件之後,就可以通過呼叫invoke方法來呼叫該Method物件對應的方法。
Person person = new Person();
// 獲取getAge方法
Method getAgeMethod = person.getClass().getMethod("getAge",null);
// 呼叫invoke方法來呼叫getAge方法
Integer age = (Integer) getAgeMethod.invoke(person,null);
System.out.println(age);
複製程式碼
訪問成員變數
通過Class物件的的getField()方法可以獲取該類所包含的全部或指定的成員變數Field,Filed提供瞭如下兩組方法來讀取和設定成員變數值.
-
getXxx(Object obj): 獲取obj物件的該成員變數的值, 此處的Xxx對應8中基本型別,如果該成員變數的型別是引用型別, 則取消get後面的Xxx;
-
setXxx(Object obj, Xxx val): 將obj物件的該成員變數值設定成val值.此處的Xxx對應8種基本型別, 如果該成員型別是引用型別, 則取消set後面的Xxx;
Person person = new Person();
// 獲取name成員變數Field
Field nameField = person.getClass().getDeclaredField("name");
// 啟用訪問控制許可權
nameField.setAccessible(true);
// 獲取person物件的成員變數name的值
String name = (String) nameField.get(person);
System.out.println("name = " + name);
// 設定person物件的成員變數name的值
nameField.set(person, "lisi");
System.out.println(person);
複製程式碼
運算元組
在Java的java.lang.reflect包中存在著一個可以動態運算元組的類,Array提供了動態建立和訪問Java陣列的方法。Array允許在執行get或set操作進行取值和賦值。在Class類中與陣列關聯的方法是:
方法 | 說明 |
---|---|
public native Class<?> getComponentType() | 返回表示陣列元素型別的Class,即陣列的型別 |
public native boolean isArray() | 判定此Class物件是否表示一個陣列類 |
java.lang.reflect.Array中的常用靜態方法如下:
-
newInstance(Class<?> componentType, int length)
-
newInstance(Class<?> componentType, int... dimensions)
-
int getLength(Object array)
-
Object get(Object array, int index)
-
void set(Object array, int index, Object value)
實現通用陣列複製功能,其程式碼如下:
public class GenericityArray {
public static <T> T[] copy(T[] clazz) {
return (T[]) Array.newInstance(
clazz.getClass().getComponentType(),
clazz.length);
}
public static void main(String[] args) {
Integer[] array = {1, 2, 3};
Integer[] copyArray = GenericityArray.copy(array);
System.out.println(copyArray.length);
}
}
複製程式碼
使用反射獲取泛型資訊
為了通過反射操作泛型以迎合實際開發的需要, Java新增了java.lang.reflect.ParameterizedType
、 java.lang.reflect.GenericArrayType
、java.lang.reflect.TypeVariable
、java.lang.reflect.WildcardType
。
型別 | 含義 |
---|---|
ParameterizedType | 一種引數化型別, 比如Collection |
GenericArrayType | 一種元素型別是引數化型別或者型別變數的陣列型別 |
TypeVariable | 各種型別變數的公共介面 |
WildcardType | 一種萬用字元型別表示式, 如? extends Number |
其中, 我們可以使用ParameterizedType來獲取泛型資訊.
public class Client {
private Map<String, Object> objectMap;
public void test(Map<String, User> map, String string) {
}
public Map<User, Bean> test() {
return null;
}
/**
* 測試屬性型別
*
* @throws NoSuchFieldException
*/
@Test
public void testFieldType() throws NoSuchFieldException {
Field field = Client.class.getDeclaredField("objectMap");
Type gType = field.getGenericType();
// 列印type與generic type的區別
System.out.println(field.getType());
System.out.println(gType);
System.out.println("**************");
if (gType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) gType;
Type[] types = pType.getActualTypeArguments();
for (Type type : types) {
System.out.println(type.toString());
}
}
}
/**
* 測試引數型別
*
* @throws NoSuchMethodException
*/
@Test
public void testParamType() throws NoSuchMethodException {
Method testMethod = Client.class.getMethod("test", Map.class, String.class);
Type[] parameterTypes = testMethod.getGenericParameterTypes();
for (Type type : parameterTypes) {
System.out.println("type -> " + type);
if (type instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println("\tactual type -> " + actualType);
}
}
}
}
/**
* 測試返回值型別
*
* @throws NoSuchMethodException
*/
@Test
public void testReturnType() throws NoSuchMethodException {
Method testMethod = Client.class.getMethod("test");
Type returnType = testMethod.getGenericReturnType();
System.out.println("return type -> " + returnType);
if (returnType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println("\tactual type -> " + actualType);
}
}
}
}
複製程式碼
使用反射獲取註解
使用反射獲取註解資訊的相關介紹, 請參看Java註解實踐