java.lang.Class 是 Java 反射的核心入口。在 J2SE 中它提供豐富的 API(getFields()、getMethods()、getClassLoader() 等),但在 CLDC/MIDP 環境下,為了精簡體積,Class 被大幅裁剪。本文結合原始碼片段,剖析 J2ME 版 Class 的結構特徵、類初始化流程與開發實務上的限制。
public final class Class {
private Class() {} // 私有建構子,禁止外部實例化
public static native Class forName(String className)
throws ClassNotFoundException; // 核心靜態工廠:載入並回傳 Class 物件
public native Object newInstance()
throws InstantiationException, IllegalAccessException;
public native boolean isInstance(Object obj);
public native boolean isAssignableFrom(Class cls);
public native boolean isInterface();
public native boolean isArray();
public native String getName();
// 取得資源流(J2ME 常用於讀取 /res/ 目錄下的圖片、資料檔)
public java.io.InputStream getResourceAsStream(String name) {
try {
if (name.length() > 0 && name.charAt(0) == '/') {
name = name.substring(1);
} else {
String className = this.getName();
int dotIndex = className.lastIndexOf('.');
if (dotIndex >= 0) {
name = className.substring(0, dotIndex + 1).replace('.', '/') + name;
}
}
return new ResourceInputStream(name);
} catch (java.io.IOException x) {
return null;
}
}
// 類初始化狀態機
private static final int VERIFIED = 1;
private static final int IN_PROGRESS = 2;
private static final int INITIALIZED = 3;
private static final int ERROR = 4;
private native Class getSuperclass();
private transient Object vmClass;
private int status;
private Thread thread;
void initialize() throws Throwable {
verify();
synchronized (this) {
while (status == IN_PROGRESS && thread != Thread.currentThread()) {
try { wait(); } catch (InterruptedException e) {}
}
if (status == IN_PROGRESS && thread == Thread.currentThread()) return;
if (status == INITIALIZED) return;
if (status == ERROR) throw new Error("NoClassDefFoundError:" + getName());
status = IN_PROGRESS;
thread = Thread.currentThread();
}
try {
Class s = getSuperclass();
if (s != null && s.status != INITIALIZED) s.initialize();
invoke_clinit(); // 執行 靜態初始化區塊
synchronized (this) {
status = INITIALIZED;
thread = null;
notifyAll();
}
} catch (Throwable e) {
synchronized (this) {
status = ERROR;
thread = null;
notifyAll();
throwError(e);
}
}
}
synchronized void verify() throws Throwable {
if (status < VERIFIED) {
getSuperclass().verify();
invoke_verifier(); // 位元碼驗證
status = VERIFIED;
}
}
// ... 省略部分私有方法
}
private Class() 確保只能由 JVM 透過 forName() 或類載入過程產生實例。forName()、newInstance()、isInstance()、isAssignableFrom()、isInterface()、isArray()、getName()、getResourceAsStream()。無法列舉欄位、方法、建構子,也無 getClassLoader()。initialize() 先呼叫 verify()(遞迴驗證父類別),再執行 invoke_clinit()。狀態機(VERIFIED、IN_PROGRESS、INITIALIZED、ERROR)確保多執行緒安全與循環依賴檢測。| 限制 | 影響 | 替代方案 |
|---|---|---|
| 無法取得欄位/方法清單 | 無法實作通用序列化、ORM、依賴注入框架 | 採用固定協議(如手寫 toByteArray()/parse())、或編譯期產生代碼(Annotation Processor / Polish 預處理) |
| 無 ClassLoader | 無法動態載入外部 JAR、熱插拔 | 所有類別必須打包在同一 JAR;若需模組化,請在建構期用 Ant/Polish 依條件編譯 |
無 getClassLoader() | 無法用 ClassLoader.getResourceAsStream() 統一讀取資源 | 統一用 Class.getResourceAsStream(),資源放在與類別相同包路徑或 /res/ 根路徑 |
J2ME 裝置通常單一 JVM 執行單一 MIDlet,載入順序為:
java.*、javax.microedition.*)forName() 或首次主動使用觸發)因無複雜類載入器層級,ClassNotFoundException 多半源於:
MIDlet-n 類別名拼寫錯誤forName() 載入的類別 → 需在 proguard.cfg 加入 -keep class ** { *; } 或保留特定類別。理解 J2ME 版 Class 的精簡本質,能避開「想用反射卻發現 API 不存在」的坑。設計階段即以「靜態綁定、建構期代碼產生」取代動態反射,是輕量級 Java 環境的最佳實踐。