Java——是否確實的 “純面向對象”?讓我們深入到 Java 的世界,試圖來證實它。
在我剛開始學習 Java 的前面幾年,我從書本裡知道了 Java 是遵循 “面向對象編程范式(Object Oriented Programming paradigm)”的。在 Java 世界內一切都是對象,甚至包括字符串(String)這些都是對象(在 C 語言中,字符串是字符數組),那時候,我認為 Java 是一種面向對象的語言。
但是在後來,我在互聯網站上陸續看到不少開發者說 “Java 實際上不是純粹的面向對象,因為並不是所有的東西在 Java 世界都是一個對象”。他們很多的論點都可以概括為以下兩點:
在那時,由於個人知識經驗儲備有限,我又很容地相信上面的論點,並且也開始認為 “Java 不是純粹的面向對象編程語言”。
程序員使用的水杯到了更後來,在我的一次 JVM 學習過程中,我有了新的發現:
JVM 在創建對象的時候,實際上會創建兩個對象:
例如,在下面的 Java 語句中,將有兩個對象被創建:
Employee emp = new Employee ();
一個是實例對象 emp ;另一個則是 Class 對象,我們可以通過 Employee.class 引用到它;這個 Class 對象擁有所有的這個類定義的靜態變量和靜態方法,同時,如果我們訪問通過 emp 對象來訪問靜態內容,會發現它其實指向的對象就是 Employee.class 。
這也揭開了另一個迷:為什麼靜態內容在一個對象中(不管是 emp 還是 emp2)改變了,在另一個對象中也同時改變,因為這兩個對象改變的都是在 Employee.class 同一個對象裡面的內容。
現在,上面說到的第一個論點我們要取消了。因為,靜態內容確實被證實屬於一個對象。
但是我們還要確認第二個論點:正如早前提到的,原始類型在 Java 中不是對象,它們無法做類似對象的操作。為了解決這個問題,Java 官方為每一個原始類型推出了對應的包裝類(比如:Integer 對應 int,Long 對應 long,Character 對應 char),所以,其實現在我們可以為原始類型創建一個包裝對象,同時對它們做對象相關的操作。並且,由於自動拆裝箱,我們可以把一個原始類型值賦值給它對應的包裝類的引用。但是我們仍然不能對這些原始類型做對象的操作——我們需要創建對應包裝類的對象。
例如:
Integer obj = new Integer (5); // here we can do i.toString (); int i = 5; // but we can't do i.toString () here
到目前為止,從一個最終用戶的角度上來看的,我們可以確認 “原始類別不是對象”。( Java 開發人員是 Java 的最終用戶,因為我們正在使用它,而不是創造它 )。
如果站在 JVM 的視角,會有新的發現:
其實,在 JVM 看來它把所有的 “原始類型” 都是當作對象處理” ,要證明這一點可以通過 Class 類的源代碼或者 Javadoc 中 Class 類的說明。
根據 java.lang.Class 類的源代碼,該類的注釋是:
Java 官方描述:
Class 類的實例表示正在運行的 Java 應用程序的類和接口。像枚舉是一種類和注解則是一種接口。每個數組也屬於被反射作為由具有相同的元素類型和尺寸的數目的所有陣列共享一類對象的類。原始的 Java 類型(boolean, byte, char, short, int, long, float, and double)和關鍵字 void 也表示為 Class 對象。
同時也根據 Javadoc 中對 Class.isPrimitive ()方法的定義,來判斷
Java 官方描述:
public boolean isPrimitive ()
判斷指定的 Class 對象是否代表一個基本類型。
一共有 9 種設定好的 Class 對象來表示對應的基本類型和 void 關鍵字。這些對象都是由 JVM 創建的。…
return
當且僅當該類表示一個真正的基本類型
以上都說明,在 JVM 內部,其實原始類型就是對象。
當你打開 Javadoc 對 Class 類的定義中,通過 “CTRL+F ” 查找關鍵字 “primitive”, 將會發現證據在表面 “在 JVM 裡,它把基本類型當作對象來處理的”。
我們可以再來看一個例子: Integer.TYPE,在這部分文檔清晰記錄著:
Java 官方描述:
public static final Class<Integer> TYPE
The Class instance representing the primitive type int.
以上都說明,在 JVM 內部,其實原始類型就是對象。
那麼,既然說 “JVM”會為所有的基本類型創建一個對象,那我們為什麼還那麼常用 “原始類型”, 而不是直接使用對應的包裝類對象呢?
這是因為,為 “原始類型” 創建的對象,在 JVM 內部是很輕量級的,相對與我們直接創建的對應包裝類對象做了許多優化; 也正因為輕量的緣故,這些原始類的功能就比較少(例如我們不能調用其內部的方法,因為他們內部已經優化成沒有方法了)
使用實際的例子來說明,為什麼我們更應該使用 “原始類型”:
“原始類型”有更快的速度(例如,下面的代碼執行,在我們的機器上需要 9 秒,但當我把 Long 改成 long 之後,0 秒內就完成了)
public static void main (String[] args) { long millis = System.currentTimeMillis (); Long sum = 0L; // uses Long, not long for (long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; } System.out.println (sum); System.out.println ((System.currentTimeMillis () - millis) / 1000); }
“原始類型”允許我們直接使用 “==”來進行比較
new Integer (3) == new Integer (3); // false new Integer (100) == new Integer (100); // false Integer.valueOf (5) == Integer.valueOf (5); //true Integer.valueOf (200) == Integer.valueOf (200); //false
我們注意看第四句,輸出結果確實為 “false” 。這個是因在 [-128; 127] 這個區間的 265 個整數會被 JVM 緩存存放, 所以在這個區間, JVM 返回相同的對象;然而,超出這個區間, JVM 就不再有緩存了,將會創建新的對象,所以結果是不等的。
所以總結一下是: 在 JVM 內部,原始類型就是被當作對象來處理的。但是我們開發者直接把 “原始類型” 當作對象使用,開發者應該使用對應的包裝來。
以上就是為什麼我說 “ Java 確實是一個純粹的面向對象語言 ”的證實過程。如果你們對這個有什麼其他的觀點,請在評論留言,一起討論