Java 的反射、內省,你會用嗎?
你好,我是看山。
Java的反射和內省是兩個在運行時操作類和對象的強大機制,它們之間存在關聯和區別。很多時候我們用錯了,就會有性能上的損失。
概念
反射(Reflection)
反射是Java語言的一個特性,允許程序在運行時檢查和操作類、方法、字段等。反射可以動態地獲取類的所有屬性和方法,并且可以動態調用這些方法。
反射強調的是運行狀態,即在程序運行時能夠訪問和修改類的狀態或行為。反射提供了更底層的類結構和行為訪問機制。
反射的核心類都在java.reflect包下,主要類如下:
圖片
圖片
內省(Introspection)
內省是基于反射實現的,主要用于操作符合JavaBean規范的類。
JavaBean是一種特殊的Java類,通常用于封裝多個屬性為一個單一的對象。
內省機制通過反射獲取屬性描述器(PropertyDescriptor),然后可以方便地獲取和設置屬性值。
內省操作只針對JavaBean,只有符合JavaBean規則的類的成員才可以采用內省API進行操作。
內省的核心類在java.beans包下,主要類如下:
圖片
關系與區別
- 適用范圍:
反射可以操作任何類的所有成員,包括私有成員。
內省主要針對JavaBean,只能操作符合JavaBean規范的類的成員。
- 操作方式:
- 反射是先得到類的字節碼(Class)后再進行各種操作。
- 內省是先得到屬性描述器(PropertyDescriptor)后再進行各種操作。
- 復雜度:
- 反射提供了更底層的訪問機制,使用起來相對復雜。
- 內省提供了一套比較簡單和有層次的API,更專注于JavaBean的屬性操作。
- 應用場景:
- 反射適用于需要動態獲取和調用類信息的場景,如框架開發。
- 內省適用于需要操作JavaBean屬性的場景。
來點小栗子
使用反射創建對象并調用方法
我們通過反射創建對象并調用指定方法:
public static void main(String[] args)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException,
IllegalAccessException, NoSuchFieldException {
final Class<?> clazz = MyClass.class;
// Class<?> clazz = new MyClass().getClass();
// Class<?> clazz = Class.forName("cn.howardliu.tutorials.core.reflect.ReflectDemo.MyClass");
final Constructor<?> constructor = clazz.getConstructor();
// Constructor<?> constructor = clazz.getConstructor(String.class);
final Object o = constructor.newInstance();
final Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(o, "看山");
final Method method = clazz.getMethod("echoName");
final Object result = method.invoke(o);
System.out.println(result);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
publicstaticclass MyClass {
private String name;
public String echoName() {
return name;
}
}
首先,我們獲取class對象,有三種方式:
- 通過類名直接獲取:Class<?> clazz = MyClass.class;
- 通過實例獲取:Class<?> clazz = new MyClass().getClass();
- 通過加載類:Class<?> clazz = Class.forName("cn.howardliu.tutorials.core.reflect.ReflectDemo.MyClass"); 。
然后獲取構造器對象,使用Class對象的getConstructor()或getDeclaredConstructor()方法獲取構造器對象。
- 無參構造函數:Constructor<?> constructor = clazz.getConstructor();
- 有參構造函數:Constructor<?> constructor = clazz.getConstructor(String.class); 。
使用newInstance()創建實體對象:final Object o = constructor.newInstance();。
示例中我們對name屬性賦值,首先得獲取Field對象,可以用getDeclaredField()或者getField()。
因為name屬性是非public的,調用setAccessible設置可訪問,然后賦值。
最后就是獲取Method對象,使用invoke方法實現對象的動作。
以上就是反射常用的邏輯了。反射還提供了基于枚舉的API,有需要的時候可以用一用。
通過內省給對象賦值
內省就是轉為JavaBean準備的,主要包括下面幾個類:
- Introspector類:這是內省API的核心類,提供了獲取BeanInfo對象的方法,例如Introspector.getBeanInfo()方法。
- BeanInfo類:這個類包含了關于一個對象的所有Bean屬性信息,包括屬性的描述符(PropertyDescriptor)。
- PropertyDescriptor類:表示一個JavaBean屬性的信息,包括getter和setter方法。
以下是一個簡單的示例,我們首先獲取User的JavaBean信息BeanInfo,然后找到屬性描述符PropertyDescriptor列表。
使用屬性的讀方法獲取數據,使用寫方法賦值。
public static void main(String[] args)
throws IntrospectionException, InvocationTargetException, IllegalAccessException {
final BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
final User user = new User();
// 遍歷所有屬性描述符
for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
System.out.println("Property Name: " + prop.getName());
// 獲取getter方法
final Method readMethod = prop.getReadMethod();
if (readMethod != null) {
Object value = readMethod.invoke(user);
System.out.println("Property Value: " + value);
}
if ("username".equals(prop.getName())) {
// 獲取setter方法
final Method writeMethod = prop.getWriteMethod();
if (writeMethod != null) {
Object value = writeMethod.invoke(user, "看山");
System.out.println("Property Value: " + value);
}
}
}
System.out.println(user);
}
@Data
publicstaticclass User {
private String username;
}
在JavaBean的操作方面,內省確實比反射更方便。
文末總結
今天通過示例介紹了Java的反射和內省,下次我們看看在Bean賦值方面,兩者的性能差異有多少。