關於「Java 是否為純粹面向物件語言」的爭論由來已久。支持者指出一切皆物件(連 String 也是),反對者則舉出 static 成員不屬於實例、八大基本型別無法呼叫方法為由反駁。本文從 JVM 記憶體模型、Class 物件機制、基本型別內部表達三個維度,還原 Java 物件模型的真實面貌。
static 變數/方法不屬於任何實例,似乎違背「一切皆物件」。int、boolean 等無法 i.toString(),需包裝類 Integer 代勞。當執行 Employee emp = new Employee(); 時,JVM 實際建立兩個物件:
emp 參考,儲存非靜態欄位。Employee.class,唯一對應該類別,儲存靜態欄位、靜態方法、方法表、常數池等中繼資料。
所有實例共享同一個 Employee.class,因此透過 emp.staticField 或 Employee.staticField 存取的都是同一記憶體位址。這證明 靜態成員確實屬於一個物件——Class 物件,首個反駁論點不成立。
int i = 5;
// i.toString(); // 編譯錯誤
Integer obj = new Integer(5);
obj.toString(); // 合法
語法層面確實無法對基本型別呼叫方法,需自動/手動裝箱轉換為包裝類物件。從「最終使用者」角度,基本型別確非物件。
查閱 java.lang.Class 原始碼與 Javadoc:
Class 類別註解:「基本 Java 型別(boolean, byte, char, short, int, long, float, double)和關鍵字 void 也表示為 Class 物件。」
Class.isPrimitive() 文件:「共有 9 個預先建立的 Class 物件分別表示基本型別與 void,由 JVM 建立。」
Integer.TYPE:
public static final Class<Integer> TYPE代表基本型別int的 Class 實例。
結論:在 JVM 內部,每個基本型別都有對應的 Class 物件表示,JVM 將其視為物件處理。
JVM 為基本型別建立的 Class 物件極度輕量,移除方法表、欄位表等中繼資料,僅保留型別標識。直接操作基本型別避免物件頭、參考指向、堆分配、GC 掃描等開銷。
// 使用 Long 包裝類:約 9 秒
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i;
// 使用 long 基本型別:< 1 秒
long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i;
new Integer(3) == new Integer(3); // false(不同物件)
Integer.valueOf(5) == Integer.valueOf(5); // true(-128~127 快取)
Integer.valueOf(200) == Integer.valueOf(200); // false(超出快取範圍)
基本型別 == 比較數值,包裝類 == 比較參考,equals() 比較數值。這是基本型別保留的語義優勢。
| 層次 | 基本型別地位 | 靜態成員地位 |
|---|---|---|
| 語言語法(開發者視角) | 非物件,需包裝類代理 | 屬於類別,不屬於實例 |
| JVM 執行模型 | 有對應 Class 物件,被視為物件處理 | 屬於 Class 物件,確為物件 |
Java 在語言設計上為效能妥協保留基本型別,但在執行模型上維持了「一切皆物件」的一致性。這種分層設計兼顧了效能與理論純粹性,也是 Java 能在企業級高效能場景長期佔據優勢的關鍵原因之一。