?的反射(Reflection)是一種強大的機制,允許程序在運行時動態獲取類的信息、操作類的成員(屬性、方法、構造器),甚至修改類的行為。它是框架開發(如 Spring、MyBatis)、單元測試工具(如 JUnit)的核心技術之一。
一、認識反射
反射的核心是在運行時獲取類的元數據(類的結構信息),突破了傳統編程 “編譯時確定類型” 的限制。例如:
運行時判斷任意對象的所屬類;
運行時構造任意類的對象;
運行時獲取 / 修改類的屬性、調用類的方法(包括私有成員);
運行時處理注解。
二、Class 類詳解
在 ?中,Class?類是反射的入口。每個類被加載到 JVM 時,會生成唯一的?Class?對象,存儲該類的所有元數據(如類名、父類、接口、屬性、方法等)。
2.1 獲取 Class 對象的 3 種方式
// 方式1:通過 類名.class(編譯時已知類)
Class<String> stringClass = String.class;
// 方式2:通過 對象.getClass()(已知對象)
String str = "hello";Class<? extends String> strClass = str.getClass();
// 方式3:通過 Class.forName("全限定類名")(動態加載,最常用)
try {
Class<?> userClass = Class.forName("com.example.User"); }
catch (ClassNotFoundException e) {
e.printStackTrace();
}
2.2 Class 類的常用方法
方法 | 說明 |
getName() | 獲取類的全限定名(如?.lang.String) |
getSimpleName() | 獲取類的簡單名(如?String) |
getSuperclass() | 獲取父類的?Class?對象 |
getInterfaces() | 獲取實現的接口數組 |
getFields() | 獲取所有公有屬性(含父類) |
getDeclaredFields() | 獲取所有屬性(含私有,不含父類) |
getMethods() | 獲取所有公有方法(含父類) |
getDeclaredMethods() | 獲取所有方法(含私有,不含父類) |
getConstructors() | 獲取所有公有構造器 |
getDeclaredConstructors() | 獲取所有構造器(含私有) |
三、Class 類與多態
多態的本質是 “父類引用指向子類對象”,但反射可以突破多態的表象,直接操作子類或父類的真實信息。
示例:通過反射獲取多態對象的真實類信息
假設有繼承關系:Animal(父類)→?Dog(子類)。
class?Animal?{
?public?void?eat()?{
?System.out.println("Animal eat");
? }?
}
class?Dog?extends?Animal?{?
@Override?
public?void?eat()?{?
System.out.println("Dog eat");?
}
?}
public?class?PolymorphismDemo?{
????public?static?void?main(String[]?args)?{
????????Animal?animal =?new?Dog();?// 多態:父類引用指向子類對象
????????
????????// 傳統方式調用方法(表現多態)
????????animal.eat();?// 輸出:Dog eat
????????
????????// 反射獲取真實類的信息
????????Class<?>?realClass =?animal.getClass();?
????????System.out.println("真實類名:"?+?realClass.getSimpleName());?// 輸出:Dog
????????
????????// 反射調用父類的方法(繞過多態)
????????try?{
????????????Method?parentEat =?realClass.getSuperclass().getMethod("eat");
????????????parentEat.invoke(animal);?// 輸出:Animal eat(調用了父類的原始方法)
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
輸出結果:
Dog eat
真實類名:Dog
Animal eat
四、反射創建類對象
通過反射可以動態創建類的實例,即使類的構造器是私有的(需設置?setAccessible(true))。
4.1 無參構造創建對象
class?User?{
????private?String?name;
????public?User()?{?System.out.println("無參構造被調用");?}
public?User(String?name)?{?this.name =?name;?}
}
public?class?CreateObjectDemo?{
????public?static?void?main(String[]?args)?{
????????try?{
????????????// 1. 獲取User的Class對象
????????????Class<?> userClass = Class.forName("com.example.User");
????????????
????????????// 2. 通過無參構造創建實例(等價于 new User())
????????????User?user =?(User)?userClass.getDeclaredConstructor().newInstance();
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
輸出:無參構造被調用
4.2 有參構造創建對象
public?class?CreateObjectWithArgsDemo?{
????public?static?void?main(String[]?args)?{
????????try?{
????????????Class<?>?userClass =?Class.forName("com.example.User");
????????????
????????????// 獲取有參構造器(參數類型為String)
????????????Constructor<?>?constructor =?userClass.getDeclaredConstructor(String.class);
????????????
????????????// 創建實例(等價于 new User("xxx"))
????????????User?user =?(User)?constructor.newInstance("xxx");
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
4.3 私有構造器創建對象(突破訪問限制)
class?SecretClass?{
private?SecretClass()?{?System.out.println("私有構造被調用");?
}
}
public?class?CreatePrivateObjectDemo?{
????public?static?void?main(String[]?args)?{
????????try?{
????????????Class<?>?secretClass =?Class.forName("com.example.SecretClass");
????????????
????????????// 獲取私有構造器
????????????Constructor<?>?privateConstructor =?secretClass.getDeclaredConstructor();
????????????
????????????// 允許訪問私有成員(關鍵!)
????????????privateConstructor.setAccessible(true);
????????????
????????????// 創建實例
????????????SecretClass?instance =?(SecretClass)?privateConstructor.newInstance();
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
輸出:私有構造被調用
五、反射調用類方法
通過反射可以調用任意對象的方法(包括私有方法),甚至可以調用未實現的方法(動態代理的基礎)。
5.1 調用公有方法
class?Calculator?{
public?int?add(int?a,?int?b)?{?return?a +?b;?}
}
public?class?InvokeMethodDemo?{
????public?static?void?main(String[]?args)?{
????????try?{
????????????Calculator?calc =?new?Calculator();
????????????Class<?>?calcClass =?calc.getClass();
????????????
????????????// 獲取add方法(參數類型為int, int)
????????????Method?addMethod =?calcClass.getMethod("add",?int.class,?int.class);
????????????
????????????// 調用方法(等價于 calc.add(3, 5))
????????????int?result =?(int)?addMethod.invoke(calc,?3,?5);
????????????System.out.println("計算結果:"?+?result);?// 輸出:8
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
5.2 調用私有方法(突破訪問限制)
class?PrivateMethodClass?{
????private?String?formatName(String?name)?{
????????return?"Hello, "?+?name +?"!";
}
}
public?class?InvokePrivateMethodDemo?{
????public?static?void?main(String[]?args)?{
????????try?{
????????????PrivateMethodClass?obj =?new?PrivateMethodClass();
????????????Class<?>?clazz =?obj.getClass();
????????????
????????????// 獲取私有方法(方法名、參數類型)
????????????Method?privateMethod =?clazz.getDeclaredMethod("formatName",?String.class);
????????????
????????????// 允許訪問私有成員
????????????privateMethod.setAccessible(true);
????????????
????????????// 調用方法(等價于 obj.formatName("xxx"))
????????????String?result =?(String)?privateMethod.invoke(obj,?"xxx");
????????????System.out.println(result);?// 輸出:Hello, xxx!
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
六、反射修改類屬性
通過反射可以直接修改對象的屬性值(包括私有屬性),甚至繞過 setter 方法。
6.1 修改公有屬性
class?Book?{
public?String?title =?"默認書名";
}
public?class?ModifyFieldDemo?{
????public?static?void?main(String[]?args)?{
????????try?{
????????????Book?book =?new?Book();
????????????Class<?>?bookClass =?book.getClass();
????????????
????????????// 獲取公有屬性title
????????????Field titleField = bookClass.getField("title");
????????????
????????????// 修改屬性值(等價于 book.title = "反射詳解")
????????????titleField.set(book, "反射詳解");
????????????
????????????System.out.println(book.title);?// 輸出:反射詳解
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
6.2 修改私有屬性(突破訪問限制)
class?User?{
????private?String?password =?"123456";}
public?class?ModifyPrivateFieldDemo?{
????public?static?void?main(String[]?args)?{
????????try?{
????????????User?user =?new?User();
????????????Class<?>?userClass =?user.getClass();
????????????
????????????// 獲取私有屬性password
????????????Field passwordField = userClass.getDeclaredField("password");
????????????
????????????// 允許訪問私有成員
????????????passwordField.setAccessible(true);
????????????
????????????// 修改屬性值(等價于 user.password = "new_password")
????????????passwordField.set(user, "new_password");
????????????
????????????// 驗證修改結果
????????????System.out.println("新密碼:"?+?passwordField.get(user));?// 輸出:new_password
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
}
}
七、類加載器
類加載器(Class Loader)負責將?.class?文件加載到 JVM 中,生成對應的?Class?對象。 采用雙親委派模型,確保類的唯一性和安全性。
7.1 類加載器的層級
引導類加載器(Bootstrap Class Loader):加載 JDK 核心類(如?.lang.*),由 C++ 實現,無法通過 ?代碼獲取。
擴展類加載器(Extension Class Loader):加載?jre/lib/ext?目錄下的 JAR 包。
應用類加載器(Application Class Loader):加載用戶項目中的類(classpath?下的類)。
7.2 示例:查看類的加載器
public?class?ClassLoaderDemo?{
????public?static?void?main(String[]?args)?{
????????// 獲取String類的加載器(引導類加載器,輸出null)
????????ClassLoader stringLoader =?String.class.getClassLoader();
????????System.out.println("String類的加載器:"?+?stringLoader);?// 輸出:null
????????
????????// 獲取當前類的加載器(應用類加載器)
????????ClassLoader selfLoader = ClassLoaderDemo.class.getClassLoader();
????????System.out.println("當前類的加載器:"?+?selfLoader);?
????????// 輸出:sun.misc.Launcher$AppClassLoader@18b4aac2
????????
????????// 獲取應用類加載器的父加載器(擴展類加載器)
????????ClassLoader parentLoader = selfLoader.getParent();
????????System.out.println("父加載器:"?+?parentLoader);?
????????// 輸出:sun.misc.Launcher$ExtClassLoader@1b6d3586
}
}
7.3 雙親委派模型的作用
當加載一個類時,類加載器會先委托父類加載器嘗試加載,直到引導類加載器。如果父類無法加載,才由當前類加載器加載。
好處:避免重復加載,防止核心類被篡改(如自定義?.lang.String?不會被加載)。
反射是 ?的 “動態之魂”,但過度使用會降低代碼可讀性和安全性(如破壞封裝性)。實際開發中,框架(如 Spring)已封裝了反射的復雜操作,開發者只需理解原理即可。建議結合源碼(如 Spring 的?BeanFactory)深入學習反射的應用。