反射(Reflection)是程序的自我分析能力,通過反射可以確定類有哪些方法、有哪些構(gòu)造方法以及有哪些成員變量。Java語言提供了反射機制,通過反射機制能夠動態(tài)讀取一個類的信息;能夠在運行時動態(tài)加載類,而不是在編譯期。反射可以應(yīng)用于框架開發(fā),它能夠從配置文件中讀取配置信息動態(tài)加載類、創(chuàng)建對象,以及調(diào)用方法和成員變量。
Java反射機制API
Java反射機制API主要是 java.lang.Class
類和 java.lang.reflect
包。
java.lang.Class類
java.lang.Class類是實現(xiàn)反射的關(guān)鍵所在,Class類的一個實例表示Java的一種數(shù)據(jù)類型,包括類、接口、枚舉、注解(Annotation)、數(shù)組、基本數(shù)據(jù)類型和void,void是“無類型”,主要用于方法返回值類型聲明,表示不需要返回值。Class沒有公有的構(gòu)造方法,Class實例是由JVM在類加載時自動創(chuàng)建的。
方法1:調(diào)用Object類的getClass()方法。
方法2:使用Class類的forName()方法。
方法3:如果T是一個Java類型,那么T.class就代表了與該類型匹配的Class對象。
public static void main( String[] args ) throws ClassNotFoundException {
// 1.通過類型class靜態(tài)變量
Class clazz;
clazz = int.class;
print(clazz);
String[] array = new String[1];
// 2.通過對象的getClass()方法
clazz = array.getClass();
print(clazz);
clazz = Class.forName("java.lang.Override");
print(clazz);
}
private static void print(Class clazz) {
System.out.println("ClassName = " + clazz.getName());
System.out.println("isAnnotation = " + clazz.isAnnotation());
System.out.println("isArray = " + clazz.isArray());
System.out.println("isPrimitive = " + clazz.isPrimitive());
System.out.println("isEnum = " + clazz.isEnum());
System.out.println("isInterface = " + clazz.isInterface());
System.out.println("---------------------");
}
每一種類型包括類和接口等,都有一個class靜態(tài)變量可以獲得Class實例。另外,每一個對象都有g(shù)etClass()方法可以獲得Class實例,該方法是由Object類提供的實例方法。
是否為接口: isInterface()
是否為數(shù)組對象: isArray()
是否是基本類型: isPrimitive()
獲得父類: class: getSuperclass()
java.lang.reflect包
java.lang.reflect包提供了反射中用到類,主要的類說明如下:
- Constructor類:提供類的構(gòu)造方法信息。
- Field類:提供類或接口中成員變量信息。
- Method類:提供類或接口成員方法信息。
- Array類:提供了動態(tài)創(chuàng)建和訪問Java數(shù)組的方法。
- Modifier類:提供類和成員訪問修飾符信息。
示例代碼如下:
public static void main(String[] args) {
try {
// 動態(tài)加載xx類的運行時對象
Class c = Class.forName("java.lang.String");
// 獲取成員方法集合
Method[] methods = c.getDeclaredMethods();
// 遍歷成員方法集合
for (Method method : methods) {
// 打印權(quán)限修飾符,如public、protected、private
System.out.print(Modifier.toString(method.getModifiers()));
// 打印返回值類型名稱
System.out.print(" " + method.getReturnType().getName() + " ");
// 打印方法名稱
System.out.println(method.getName() + "();");
}
} catch (ClassNotFoundException e) {
System.out.println("找不到指定類");
}
}
- 通過Class的靜態(tài)方法forName(String)創(chuàng)建某個類的運行時對象,其中的參數(shù)是類全名字符串,如果在類路徑中找不到這個類則拋出ClassNotFoundException異常。
- 通過Class的實例方法getDeclaredMethods()返回某個類的成員方法對象數(shù)組。
- method.getModifiers()方法返回訪問權(quán)限修飾符常量代碼,是int類型,例如1代表public,這些數(shù)字代表的含義可以通過Modifier.toString(int)方法轉(zhuǎn)換為字符串。
- 通過Method的getReturnType()方法獲得方法返回值類型,然后再調(diào)用getName()方法返回該類型的名稱。
- method.getName()返回方法名稱。
創(chuàng)建對象
反射機制提供了另外一種創(chuàng)建對象方法,Class類提供了一個實例方法newInstance(),通過該方法可以創(chuàng)建對象。
下面兩條語句實現(xiàn)了創(chuàng)建字符串String對象。
Class clz = Class.forName("java.lang.String");
String str = (String) clz.newInstance();
這兩條語句相當(dāng)于String str = new String()語句。另外,需要注意newInstance()方法有可以會拋出
- InstantiationException表示不能實例化異常
- IllegalAccessException是不能訪問構(gòu)造方法異常。
調(diào)用構(gòu)造方法
調(diào)用方法newInstance()創(chuàng)建對象,這個過程中需要調(diào)用構(gòu)造方法,上面的代碼只是調(diào)用了String的默認(rèn)構(gòu)造方法。如果想要調(diào)用非默認(rèn)構(gòu)造方法,需要使用Constructor對象,它對應(yīng)著一個構(gòu)造方法,獲得Constructor對象需要使用Class類的如下方法:
- Constructor[] getConstructors():返回所有公有構(gòu)造方法Constructor對象數(shù)組。
- Constructor[] getDeclaredConstructors():返回所有構(gòu)造方法Constructor對象數(shù)組。
- Constructor getConstructor(Class... parameterTypes):根據(jù)參數(shù)列表返回一個公有Constructor對象。參數(shù)parameterTypes是Class數(shù)組,指定構(gòu)造方法的參數(shù)列表。
- Constructor getDeclaredConstructor(Class... parameterTypes):根據(jù)參數(shù)列表返回一個Constructor對象。參數(shù)parameterTypes同上。
private static void printConstructor() throws Exception {
Class clazz = String.class;
System.out.println(clazz.newInstance());
Class<?> [] parameterTypes = {String.class};
final Constructor constructor = clazz.getConstructor(parameterTypes);
Object[] initArgs = {"666"};
String str = (String) constructor.newInstance(initArgs);
System.out.println(str);
}
Java反射機制能夠在運行時動態(tài)加載類,而不是在編譯期。在一些框架開發(fā)中經(jīng)常將要實例化的類名保存到配置文件中,在運行時從配置文件中讀取類名字符串,然后動態(tài)創(chuàng)建對象,建立依賴關(guān)系。采用new創(chuàng)建對象依賴關(guān)系是在編譯期建立的,反射機制能夠?qū)⒁蕾囮P(guān)系推遲到運行時建立,這種依賴關(guān)系動態(tài)注入進(jìn)來稱為依賴注入。
調(diào)用方法
通過反射機制還可以調(diào)用方法,這與調(diào)用構(gòu)造方法類似。調(diào)用方法需要使用Method對象,它對應(yīng)著一個方法,獲得Method對象需要使用Class類的如下方法:
- Method[] getMethods():返回所有公有方法Method對象數(shù)組。
- Method[] getDeclaredMethods():返回所有方法Method對象數(shù)組。
- Method getMethod(String name, Class... parameterTypes):通過方法名和參數(shù)類型返回公有方法Method對象。參數(shù)parameterTypes是Class數(shù)組,指定方法的參數(shù)列表。
- Method getDeclaredMethod(String name, Class... parameterTypes):通過方法名和參數(shù)類型返回方法Method對象。參數(shù)parameterTypes同上。
private static void testMethods() throws Exception {
// 正常實現(xiàn)
final LocalDate now = LocalDate.now();
System.out.println(now);
final LocalDate date = LocalDate.of(2020, 1, 2);
System.out.println(date);
System.out.println(date.getYear());
// 使用 method 方式
final Class<LocalDate> clazz = LocalDate.class;
// 調(diào)用靜態(tài)方法
Object obj = clazz.getMethod("now").invoke(null);
LocalDate localDate = (LocalDate) obj;
System.out.println(obj);
// 調(diào)用靜態(tài)方法的有參方法
Class<?> [] parameterTypes = {int.class, int.class, int.class};
Object[] args = {2002, 1, 2};
obj = clazz.getMethod("of", parameterTypes).invoke(null, args);
System.out.println(obj);
// 調(diào)用實例的無參方法
obj = clazz.getMethod("getYear").invoke(localDate);
System.out.println(obj);
}
調(diào)用成員變量
通過反射機制還可以調(diào)用成員變量,調(diào)用方法需要使用Field對象,它對應(yīng)著一個方法,獲得Field對象需要使用Class類的如下方法:
- Field[] getFields():返回所有公有成員變量Field對象數(shù)組。
- Field[] getDeclaredFields():返回所有成員變量Field對象數(shù)組。
- Field getField(String name):通過指定公共成員變量名返回Field對象。
- Field getDeclaredField(String name):通過指定成員變量名返回Field對象。
private static void testFiled() throws Exception {
// 已知 Exception 有一個成員變量 private String detailMessage;
Throwable exception = new Throwable("hello");
System.out.println(exception.getMessage());
final Class<? extends Throwable> aClass = exception.getClass();
// 獲取非靜態(tài)方法的字段
final Field detailMessageField = aClass.getDeclaredField("detailMessage");
detailMessageField.setAccessible(true);
// 非靜態(tài)的 get
final Object obj = detailMessageField.get(exception);
System.out.println(obj);
// 非靜態(tài)的 set
detailMessageField.set(exception, "new value");
System.out.println(exception.getMessage());
// 獲取靜態(tài)字段的值
// private static final long serialVersionUID = -3042686055658047285L;
final Field serialVersionUIDField = aClass.getDeclaredField("serialVersionUID");
serialVersionUIDField.setAccessible(true);
System.out.println(serialVersionUIDField.get(null));
}
輸出
hello
hello
new value
-3042686055658047285
設(shè)置成員變量accessible標(biāo)志為true,accessible是可訪問性標(biāo)志,值為 true 則指示反射的對象在使用時應(yīng)該取消Java語言訪問檢查。值為false則指示反射的對象應(yīng)該實施Java語言訪問檢查。不僅是成員變量,方法和構(gòu)造方法也可以通過setAccessible(true)設(shè)置,實現(xiàn)對私有方法和構(gòu)造方法的訪問。
拓展
1.Type[] java.lang.Class.getGenericInterfaces()
2.Class<?>[] java.lang.Class.getInterfaces()
- 獲取類的接口實現(xiàn)信息
1.返回實現(xiàn)接口信息的Type數(shù)組,包含泛型信息
2.返回實現(xiàn)接口信息的Class數(shù)組,不包含泛型信息
細(xì)看一下,就會發(fā)現(xiàn)其中端倪,當(dāng)你的實現(xiàn)接口中不包含泛型時,同樣調(diào)用1方法,其返回的接口信息必然不帶泛型信息的,也就是1中包含2。
如何拿到接口中定義的泛型Person?
public static void main(String[] args) {
Class clz = new MyInterface<Person>() {
@Override
public Person get() {
return null;
}
}.getClass();
// 如何拿到接口中定義的泛型Person
System.out.println(((ParameterizedType) clz.getGenericInterfaces()[0]).getActualTypeArguments()[0]);
clz = new MyAbstractClass<Person>() {
@Override
public Person get() {
return null;
}
}.getClass();
// 如何拿到抽象類中定義的泛型Person
System.out.println(((ParameterizedType) clz.getGenericSuperclass()).getActualTypeArguments()[0]);
}
static interface MyInterface<T> {
T get();
}
static abstract class MyAbstractClass<T> {
abstract T get();
}
內(nèi)省技術(shù)
PropertyDescriptor類表示JavaBean類通過存儲器導(dǎo)出一個屬性。主要方法:
1、getPropertyType(),獲得屬性的Class對象。
2、getReadMethod(),獲得用于讀取屬性值的方法;getWriteMethod(),獲得用于寫入屬性值的方法。
3、hashCode(),獲取對象的哈希值。
4、setReadMethod(Method readMethod),設(shè)置用于讀取屬性值的方法;setWriteMethod(Method writeMethod),設(shè)置用于寫入屬性值的方法;
將JavaBean中的屬性封裝起來進(jìn)行操作。在程序把一個類當(dāng)做JavaBean來看,就是調(diào)用Introspector.getBeanInfo()方法,得到的BeanInfo對象封裝了把這個類當(dāng)做JavaBean看的結(jié)果信息,即屬性的信息。需要導(dǎo)包java.beans.*。
private static void testPropertyDescriptor() throws Exception {
// 使用內(nèi)省
final Class<App> appClass = App.class;
App app = appClass.newInstance();
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("sss", appClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(app, "11111");
System.out.println(propertyDescriptor.getReadMethod().invoke(app));
BeanInfo beanInfo = Introspector.getBeanInfo(appClass);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
System.out.println(pd);
}
}