Java 反射 (Reflection)
反射允許在執行時期檢視和操作類別、方法、欄位等,常用於框架開發、測試工具、動態代理等場景。
取得 Class 物件
// 方法 1:透過類別
Class<String> clazz1 = String.class;
// 方法 2:透過實例
String str = "Hello";
Class<?> clazz2 = str.getClass();
// 方法 3:透過完整類別名稱
Class<?> clazz3 = Class.forName("java.lang.String");
類別資訊
Class<?> clazz = Person.class;
// 基本資訊
String name = clazz.getName(); // com.example.Person
String simpleName = clazz.getSimpleName(); // Person
Package pkg = clazz.getPackage();
Class<?> superClass = clazz.getSuperclass();
Class<?>[] interfaces = clazz.getInterfaces();
// 修飾符
int modifiers = clazz.getModifiers();
boolean isPublic = Modifier.isPublic(modifiers);
boolean isAbstract = Modifier.isAbstract(modifiers);
// 類型判斷
boolean isInterface = clazz.isInterface();
boolean isEnum = clazz.isEnum();
boolean isArray = clazz.isArray();
boolean isRecord = clazz.isRecord();
建構子
Class<?> clazz = Person.class;
// 取得所有公開建構子
Constructor<?>[] constructors = clazz.getConstructors();
// 取得指定建構子
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 建立實例
Person person = (Person) constructor.newInstance("Alice", 25);
// 無參數建構子
Person person2 = (Person) clazz.getDeclaredConstructor().newInstance();
欄位
Class<?> clazz = Person.class;
// 取得所有公開欄位(含繼承)
Field[] publicFields = clazz.getFields();
// 取得所有宣告的欄位(含私有)
Field[] allFields = clazz.getDeclaredFields();
// 取得指定欄位
Field nameField = clazz.getDeclaredField("name");
// 存取私有欄位
nameField.setAccessible(true);
Person person = new Person("Alice", 25);
String name = (String) nameField.get(person);
nameField.set(person, "Bob");
方法
Class<?> clazz = Person.class;
// 取得所有公開方法(含繼承)
Method[] publicMethods = clazz.getMethods();
// 取得所有宣告的方法
Method[] allMethods = clazz.getDeclaredMethods();
// 取得指定方法
Method method = clazz.getMethod("setName", String.class);
// 呼叫方法
Person person = new Person();
method.invoke(person, "Alice");
// 呼叫靜態方法
Method staticMethod = clazz.getMethod("staticMethod");
staticMethod.invoke(null);
// 呼叫私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(person);
取得泛型資訊
public class Container<T> {
private List<String> items;
}
Field field = Container.class.getDeclaredField("items");
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type[] actualTypes = pt.getActualTypeArguments();
System.out.println(actualTypes[0]); // java.lang.String
}
取得註解
Class<?> clazz = MyClass.class;
// 類別註解
Annotation[] annotations = clazz.getAnnotations();
MyAnnotation ann = clazz.getAnnotation(MyAnnotation.class);
// 方法註解
Method method = clazz.getMethod("myMethod");
if (method.isAnnotationPresent(Test.class)) {
Test test = method.getAnnotation(Test.class);
}
// 參數註解
Parameter[] params = method.getParameters();
for (Parameter param : params) {
Annotation[] paramAnns = param.getAnnotations();
}
動態代理
public interface UserService {
void save(String name);
}
// 建立代理
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[] { UserService.class },
(proxyObj, method, args) -> {
System.out.println("Before: " + method.getName());
// 這裡可以呼叫實際實作
System.out.println("After: " + method.getName());
return null;
}
);
proxy.save("Alice");
// Before: save
// After: save
實用範例
簡單的 IoC 容器
public class SimpleContainer {
private Map<Class<?>, Object> beans = new HashMap<>();
public <T> void register(Class<T> clazz) throws Exception {
T instance = clazz.getDeclaredConstructor().newInstance();
beans.put(clazz, instance);
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> clazz) {
return (T) beans.get(clazz);
}
}
物件轉 Map
public static Map<String, Object> toMap(Object obj) throws Exception {
Map<String, Object> map = new HashMap<>();
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
map.put(field.getName(), field.get(obj));
}
return map;
}
注意事項
- 效能:反射比直接呼叫慢
- 安全性:可以繞過存取控制
- 維護性:編譯時不檢查,容易出錯
- 模組化:Java 9+ 模組系統可能限制反射存取
// 效能優化:快取 Method/Field
private static final Method METHOD;
static {
try {
METHOD = MyClass.class.getMethod("myMethod");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
重點整理
- 反射允許在執行時檢視和操作程式結構
- 使用
Class.forName()或.class取得 Class 物件 setAccessible(true)可存取私有成員- 動態代理用於 AOP、Mock 等場景
- 反射效能較差,應謹慎使用
- 許多框架(Spring、Hibernate)大量使用反射