java.lang.Class 是 Java 反射的入口,每個類在 JVM 中對應唯一的 Class 對象。本文系統講解Class 對象本質、三種獲取方式、類字面常量、instanceof 與 isInstance、反射 API 核心用法、動態調用完整示例,助力掌握 Java 動態編程精髓。
.class 文件,運行時由類加載器讀入內存生成 Class 對象Class 對象包含類的完整元數據:字段、方法、構造器、註解、父類、接口等Class 實例class Candy { static { System.out.println("Loading Candy"); } }
class Gum { static { System.out.println("Loading Gum"); } }
class Cookie{ static { System.out.println("Loading Cookie"); } }
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy(); // 被動引用:僅創建對象,觸發 Candy 加載
System.out.println("After creating Candy");
Class.forName("com.zj.sample.Gum"); // 主動引用:觸發 Gum 加載
System.out.println("After Class.forName");
new Cookie(); // 觸發 Cookie 加載
System.out.println("After creating Cookie");
}
}
輸出:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
關鍵點:Class.forName() 立即觸發類初始化(執行 static 塊);new 也觸發;單純引用類字面常量 Gum.class 不 觸發初始化。
| 方式 | 代碼 | 特點 | 觸發初始化 |
|---|---|---|---|
| 對象.getClass() | obj.getClass() | 運行期已有對象 | 否(對象已存在說明類已初始化) |
| Class.forName() | Class.forName("全限定名") | 字符串動態指定、可跨類加載器 | 是(默認) |
| 類字面常量 | Type.class / int.class | 編譯期綁定、類型安全、性能最優 | 否 |
Class c1 = int.class; // 基本類型 Class 對象
Class c2 = Integer.TYPE; // 同 c1
Class c3 = Integer.class; // 包裝類 Class 對象
System.out.println(c1.getName()); // "int"
System.out.println(c3.getName()); // "java.lang.Integer"
Class c = Class.forName("com.zj.sample.Point");
Point p = (Point) c.newInstance(); // 調用無參構造器
// 需拋出 InstantiationException、IllegalAccessException
要求類有 public 無參構造器。僅能調用無參構造器,帶參構造需用 Constructor.newInstance()。
if (c.isInstance(obj)) { ... } // 等價於 obj instanceof Point
Class superClass = c.getSuperclass(); // 直接父類
Class[] interfaces = c.getInterfaces(); // 直接實現接口
// 遞歸可獲取完整繼承鏈
均實現 Member 接口,由 JVM 運行時創建:
Field:get()/set() 讀寫字段,getFields()/getDeclaredFields()Method:invoke(obj, args) 調用方法,getMethods()/getDeclaredMethods()Constructor:newInstance(args) 創建對象,getConstructors()/getDeclaredConstructors()getXxx() 返回 public 成員(含繼承);getDeclaredXxx() 返回本類聲明所有成員(含 private),不含繼承。
Class c = Class.forName("com.zj.sample.Point");
Constructor[] cons = c.getDeclaredConstructors();
for (Constructor con : cons) System.out.println(con);
// 輸出: com.zj.sample.Point(int,int)
Method[] methods = c.getDeclaredMethods();
for (Method m : methods) System.out.println(m);
// 輸出: void com.zj.sample.Point.output()
Class c = Class.forName("com.zj.sample.Point"); // 假設類名來自配置文件
Constructor[] cons = c.getDeclaredConstructors();
Constructor targetCon = cons[0]; // 取第一個構造器
// 分析構造器參數類型
Class[] paramTypes = targetCon.getParameterTypes();
Object[] paramValues = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
if (paramTypes[i].isPrimitive()) {
// 簡化:基本類型用包裝類默認值
if (paramTypes[i] == int.class) paramValues[i] = 0;
else if (paramTypes[i] == double.class) paramValues[i] = 0.0;
// ... 其他基本類型
} else {
paramValues[i] = null; // 引用類型傳 null
}
}
// 創建實例
Object obj = targetCon.newInstance(paramValues);
// 調用第一個方法(無參)
Method[] methods = c.getDeclaredMethods();
methods[0].invoke(obj, null); // 等價於 obj.output()
輸出:x=0,y=1(示例中構造器參數賦值邏輯導致)
此模式是插件系統、腳本引擎、框架核心(Spring IOC、MyBatis Mapper 代理)的基石。
setAccessible(true) 可繞過訪問檢查,但破壞封裝,需謹慎--add-opens 或 opens 聲明Class、Method、Constructor 對象,避免重複反射解析掌握 Class 機制,即掌握了 Java 動態性的核心鑰匙。從簡單的插件加載到複雜的 AOP 代理、ORM 映射,無不建立在此基礎之上。