文章目錄
- 引入
- 類加載過程
- 1. 通過 new 創建對象
- 2. 通過反射創建對象
- 2.1 觸發加載但不初始化
- 2.2 按需觸發初始化
- 2.3 選擇性初始化控制
- 核心用法
- 示例
- 1. 通過無參構造函數創建實例對象
- 2. 通過有參構造函數創建實例對象
- 3. 反射通過私有構造函數創建對象, 破壞單例模式
- 4. 通過反射獲得類的public屬性值, 演示getField與getDeclaredField兩者的區別
引入
當我們剛接觸java語言的時候, 我們最常寫的代碼應該就是初始化某個對象, 然后調用該 對象的方法。 如:
MyClass obj = new MyClass();
obj.doSth();
上面的這種用法的前提是, 我們在寫代碼的時候已經確定要去創建MyClass類的具體實例對象。
那如果我們想在代碼運行的時候才去指定具體對象的類(比如根據傳入的參數名稱確定創建的類名),普通的硬編碼方式將無法實現需求 ;反射登場了。
反射為什么可以實現呢, 這個就要先介紹一下類加載的過程了。
類加載過程
- 加載(Loading)
JVM將類的字節碼文件(.class)加載到內存,創建Class對象。 - 鏈接(Linking)
- 驗證:確保字節碼符合規范。
- 準備:為靜態變量分配內存并賦予默認值(如int初始化為0)。
- 解析:將符號引用轉換為直接引用。
- 初始化(Initialization)
執行類的靜態代碼塊(static {})和靜態變量顯式賦值。
1. 通過 new 創建對象
new關鍵字會直接觸發類的完整加載、鏈接和初始化過程:
- 若類未加載:
- 立即執行加載、鏈接,完成后強制觸發類的初始化(執行static代碼塊和初始化靜態變量)。 - 初始化完成后:調用構造函數創建對象。
示例:
// 第一次使用類時觸發初始化
MyClass obj = new MyClass();
特點:
- 類必須在編譯時已知(硬編碼依賴)。
- 初始化在對象創建時必定發生。
2. 通過反射創建對象
通過反射(Class.newInstance()或Constructor.newInstance())創建對象時,允許分階段控制類的加載過程:
2.1 觸發加載但不初始化
使用ClassLoader.loadClass()可加載類但不初始化:
ClassLoader loader = MyClass.class.getClassLoader();
Class<?> clazz = loader.loadClass("MyClass"); // 僅加載和鏈接,不初始化
此時尚未執行靜態代碼塊或靜態變量顯式賦值。
2.2 按需觸發初始化
在首次需要初始化時才觸發(如反射調用newInstance()):
Object obj = clazz.newInstance(); // 觸發初始化 → 執行static代碼塊
2.3 選擇性初始化控制
通過Class.forName可指定是否初始化:
public class Main {public static void main(String[] args) throws Exception {// 反射示例:ClassLoader loader = MyClass.class.getClassLoader();// 加載類但不初始化(第三個參數為類加載器)System.out.println("加載類但不初始化1...");Class<?> clazz2 = Class.forName("com.test.galaxy.MyClass", false, loader);// 加載類但不初始化System.out.println("加載類但不初始化2...");Class<?> clazz = loader.loadClass("com.test.galaxy.MyClass"); // 無輸出// 觸發初始化前,類的靜態代碼塊仍未執行System.out.println("準備創建對象...");Object obj = clazz.newInstance(); // 輸出:靜態代碼塊執行!// 加載類同時觸發初始化System.out.println("加載類同時觸發初始化...");Class<?> clazz1 = Class.forName("com.test.galaxy.MyClass2");}
}
class MyClass {static {System.out.println("靜態代碼塊執行!"); // 初始化觸發}
}
class MyClass2 {static {System.out.println("靜態代碼塊2執行!"); // 初始化觸發}
}
特點:
- 類的加載步驟可拆分(加載、鏈接、初始化分開觸發)。
- 初始化在需要時才發生(如通過newInstance())。
- 靈活支持運行時動態加載類(例如插件化架構)。
核心用法
反射允許程序在運行時動態獲取類的信息并操作類或對象。核心類是 Class,關鍵操作包括:
- 動態創建對象(newInstance())
- 調用方法(method.invoke())
- 訪問/修改字段(field.get()/set())
示例
1. 通過無參構造函數創建實例對象
import java.lang.reflect.Constructor;
public class ReflectionExample1 {public static void main(String[] args) {try {// 1. 獲取Class對象(觸發類加載,可能初始化)Class<?> clazz = Class.forName("com.test.galaxy.User");// 2. 獲取無參構造方法(需處理異常)Constructor<?> constructor = clazz.getDeclaredConstructor();// 3. 調用newInstance()創建實例(無參數)Object instance = constructor.newInstance();System.out.println("實例創建成功:" + instance.getClass());} catch (Exception e) {e.printStackTrace();}}
}
class User {public User() {System.out.println("無參構造函數被調用!");}
}
關鍵說明
- Class.forName():動態加載類,默認觸發初始化。
- getDeclaredConstructor():傳入空參數類型列表表示獲取無參構造方法。
- 私有構造方法處理:若構造函數是私有(private),需調用 constructor.setAccessible(true) 解除訪問限制。
2. 通過有參構造函數創建實例對象
import java.lang.reflect.Constructor;public class ReflectionExample2 {public static void main(String[] args) {try {// 1. 獲取Class對象(注意使用全限定類名)Class<?> clazz = Class.forName("com.test.galaxy.User2");// 2. 指定參數類型列表,獲取有參構造方法Class<?>[] paramTypes = {String.class, int.class}; // 參數類型順序嚴格匹配Constructor<?> constructor = clazz.getDeclaredConstructor(paramTypes);// 3. 傳遞參數值實例化對象Object[] initArgs = {"張三", 25}; // 參數值順序與類型列表一致Object instance = constructor.newInstance(initArgs);System.out.println("實例創建成功:" + instance.getClass());} catch (Exception e) {e.printStackTrace();}}
}
class User2 {private String name;private int age;public User2(String name, int age) {this.name = name;this.age = age;System.out.println("有參構造函數被調用!name=" + name + ", age=" + age);}
}
關鍵說明
- 參數類型匹配:必須精確指定參數類型(如 int.class 不能寫作 Integer.class)。
- 參數值順序:傳入的參數值順序需與聲明時一致。
- 可變長參數處理:若構造方法參數為可變長度(如 String…),類型寫為 String[].class。
3. 反射通過私有構造函數創建對象, 破壞單例模式
import java.lang.reflect.Constructor;class Singleton {private static Singleton instance;private Singleton() {// 私有構造函數}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
public class ReflectionExample3 {public static void main(String[] args) {try {// 通過正常方式獲取單例對象Singleton instance1 = Singleton.getInstance();System.out.println("正常實例:" + instance1);// 方式 1:通過反射創建新實例(直接訪問構造函數)Class<Singleton> clazz = Singleton.class;Constructor<Singleton> constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true); // 訪問私有構造函數Singleton instance2 = constructor.newInstance();// 方式 2:通過反射多次創建實例(動態控制)for (int i = 0; i < 3; i++) {Constructor<Singleton> ctor = clazz.getDeclaredConstructor();ctor.setAccessible(true);Singleton instance = ctor.newInstance();System.out.println("反射實例 " + (i+1) + ": " + instance);}// 驗證兩個實例是否相同System.out.println("instance1 == instance2 ? " + (instance1 == instance2));} catch (Exception e) {e.printStackTrace();}}
}
4. 通過反射獲得類的public屬性值, 演示getField與getDeclaredField兩者的區別
- getField() 的特點
- 只能獲取 當前類及繼承鏈中聲明為 public 的屬性;
- 無法獲取非 public 屬性;
- 可以直接訪問繼承的父類 public 屬性。
- getDeclaredField() 的特點
- 能獲取 當前類中聲明的所有屬性(包括 private/protected/public);
- 無法獲取父類聲明的屬性;
- 訪問非 public 屬性需通過 setAccessible(true)。
import java.lang.reflect.Field;class Parent {public String parentPublicField = "Parent-Public";private String parentPrivateField = "Parent-Private";
}
class Child extends Parent {public String childPublicField = "Child-Public";private String childPrivateField = "Child-Private";
}
public class ReflectionExample4 {public static void main(String[] args) {Child child = new Child();Class<?> clazz = Child.class;try {// ======================= 使用 getField() ========================// 1. 獲取子類的 public 屬性(成功)Field childPublicField = clazz.getField("childPublicField");System.out.println("[getField] 子類 public 屬性: " + childPublicField.get(child));// 2. 獲取父類的 public 屬性(成功)Field parentPublicField = clazz.getField("parentPublicField");System.out.println("[getField] 父類 public 屬性: " + parentPublicField.get(child));// 3. 嘗試獲取子類的 private 屬性(失敗,觸發異常)clazz.getField("childPrivateField");} catch (Exception e) {System.err.println("[getField 失敗] " + e.getClass().getSimpleName() + ": " + e.getMessage());}try {// ================== 使用 getDeclaredField() ======================// 1. 獲取子類的 public 屬性(成功)Field childPublicDeclaredField = clazz.getDeclaredField("childPublicField");System.out.println("[getDeclaredField] 子類 public 屬性: " + childPublicDeclaredField.get(child));// 2. 獲取子類的 private 屬性(需解除訪問限制)Field childPrivateDeclaredField = clazz.getDeclaredField("childPrivateField");childPrivateDeclaredField.setAccessible(true); // 強制訪問私有屬性System.out.println("[getDeclaredField] 子類 private 屬性: " + childPrivateDeclaredField.get(child));// 3. 嘗試獲取父類的屬性(失敗,無論是否是 public)clazz.getDeclaredField("parentPublicField");} catch (Exception e) {System.err.println("[getDeclaredField 失敗] " + e.getClass().getSimpleName() + ": " + e.getMessage());}}
}