比较Java和C++

本條目為 **Java 語言和 C++ 語言之間的比較**。

## 設計目標

C++ 和 Java 語言之間的不同可以追溯到它們各自的傳統，它們有著不同的設計目標。

* **C++** 被設計成主要用在系統性應用程式設計上的語言，對 C 語言進行了擴展。基於 C 語言這個為執行效率設計的過程序程式設計語言，C++ 添加了對這些特性的支援：靜態類型的物件導向程序設計的支援、異常處理、RAII 以及泛型。另外它還添加了一個包含泛型容器和演算法的 C++ 庫函式。
* **Java** 最初被設計用來支援網路計算。它依賴一個虛擬機來保證安全和可移植性。Java 包含一個可擴展的庫以提供一個完整的下層平台抽象。Java 是一種靜態面向物件語言，它使用的語法和 C++ 類似，但並不與之相容。為了使更多的人到使用更易用的語言，它進行了全新的設計。

不同的開發目標導致 C++ 和 Java 這兩種語言的不同的規則以及設計上的平衡點不同。
如下列出不同點：

C++ 是一門強大的語言，設計用在系統程式設計方面。Java 語言是設計成簡單易用易學習，並有一個強大的跨平台的庫。Java 函式庫對一個函式庫來說相當的大。但 Java 並不會提供所在平台的所有特性和介面。C++ 函式庫簡單健壯，提供容器和關聯數組的支援。

## 語言特性

### 語法

* Java 語法是上下文無關文法，可以用一個簡單的 LALR 語法剖析器來分析。而分析 C++ 就複雜多了；例如 `Foo<1>(3);` ，如果 Foo 是一個變數，那麼它是一個比較的運算式，但如果 Foo 是一個類範本的名字，那麼它會建立一個物件.
* C++ 允許命名空間級別的常數，變數和函式。而所有這樣的 Java 聲明必須在一個類或者介面當中.
* 在 C++ 的聲明中，一個類名可以用來聲明一個此類物件的值. Java 裡沒辦法做到這點。在 Java 裡物件不是值。在 Java 的聲明中，一個類名聲明的是對此類的一個物件的_參照_. 而在 C++ 裡與之等價的做法是用 "\*" 來聲明一個指標.
* 在 C++ 裡，"." 運算子將一個物件作為一個左指令引數來存取這個物件的成員。因為物件在 Java 裡不是值，所有的物件都通過參照來存取，剛才的做法在 Java 裡是無法實現的。在 Java 裡，"." 運算子是將一個物件的參照作為左指令引數來存取這個物件的成員。在 C++ 中和這種做法等價的是 "->".
* 在 C++ 裡，定義一個指向常數的指標（唯讀指標）是可能的，也就是說，你不能修改這個指標指向的物件的內容。函式和方法也都保證不會修改用 "const" 關鍵字的指標指向的物件的內容，是強制常數正確性的。在 Java 裡這是不可能做到的。你可以定義一個參照為 "final"（就像在 C++ 裡定義一個指標 "常數"），但這只是阻止你重新綁定這個參照；你還是可以修改這個 "final" 參照指向的物件的.
* C++ 支援 `goto` 語句；Java 強制結構化流程控制 (structured control flow)，依賴 break 標籤 和 continue 標籤 語句來提供類似於 goto 的部分功能。一些評論者指出這些標籤化的流程控制打破了結構化程式設計的單登出點的特點.
* C++ 提供了一些 Java 缺乏的低級特性。在 C++ 裡，指標可以用來操作特定的記憶體位置，這是在寫低級作業系統模組的時候必須用到的。類似的，許多 C++ 編譯期支援內聯彙編，在 Java 裡，這樣的代碼只能放在外來的庫中，而且在調用的時候只能通過 JNI 來存取這些外來庫提供的介面.

### 語意

* C++ 允許給函式 / 方法的偏好設定預設值，Java 不提供這個特性。但是方法多載可以達到同樣的效果.
* C++ 里最小的編譯單位是一個函式；Java 里最小的編譯單位是一個類。在 C++ 里，函式可以被單獨編譯。在 Java 里，要編譯和維護單獨的方法需要把它們移到超類或子類或者使用其他的代碼重構的技巧.
* C++ 允許基本類型之間的一些隱式的轉換，也允許程式設計師對於使用者自訂類型相關的隱式轉換規則。在 Java 里，只有基本類型之間變寬類型的轉換可以是隱式的；其餘的轉換需要顯式的類型轉換語法.
  * 這造成的一個後果是，雖然在 Java 和 C++ 里循環的條件（`if`、`while` 和 `for` 里的登出條件）預期的都是一個布爾表達式，但 `if(a = 5)` 這樣的代碼在 Java 里會導致編譯錯誤，因為沒有從整型到布爾的隱式變窄轉換。如果代碼是 `if(a == 5)` 的輸錯的情況那麼是很方便發現這個錯誤的。而目前的 C++ 編譯器一般來說只會針對這種情況產生一個警告.
* 對於傳參數給函式的情況，C++ 支援參照傳遞和值傳遞。在 Java 里，參數總是值傳遞的。但在 Java 里，所有的非基本類型的值都只是對於物件的參照（用 C++ 的術語來說，它們是智慧型指針）. 物件在 Java 里不是作為值直接被使用的，只有物件的參照可以被直接操作；習慣於將物件當做值直接使用的 C++ 開發者經常會把這個跟參照傳遞搞混.
* Java 內建的類型在位元組寬度和取值範圍上是被虛擬機定義好的；在 C++ 里，內建的類型有定義一個最小取值範圍，但是其他的部分（位元組寬度）可以被對映成具體平台上支援的原生類型.
  * 舉個例子，Java 字元是 16 位元的 Unicode 字元，字串是由這樣的字元組成的序列. C++ 提供窄和寬兩種字元，但實際的字元寬度是和平台相關的，視所用的字元集而定。字串可以由這兩種字元中的一種組成.
* 浮點數及其操作的精度和捨入方式在 C++ 里是平台相關的. Java 提供了一個可選的嚴格的浮點數模型，保證跨平台的一致性，不過可能會導致運行時效率比較差.
* 在 C++ 里，指針可以作為記憶體位址直接操作. Java 沒有指針 — 它只有物件參照和數組參照，這兩者都不允許直接用來存取記憶體位址。在 C++ 里可以構造一個指向指針的指針，而 Java 的參照只能指向物件.
* 在 C++ 里，指針可以指向函式或者方法（函式指針）. 在在 Java 里的等價物是物件或者介面的參照.
* 雖然有使用棧記憶體分配的物件，C++ 還是支援區域資源管理，一個用來自動管理記憶體和其他系統資源的技術，此技術支援確定性物件銷毀 (deterministic object destruction). 不過，區域資源管理在 C++ 里是不被保證的；它只是一個設計模式，所以需要依賴程式設計師遵守相關的規則. Java 通過使用垃圾搜集來支援自動記憶體管理，但對於其他的系統資源（窗口，通訊埠，執行緒），如果垃圾搜集器無法決定它們是否不再被用到，那通常還是需要顯式的釋放的.
* C++ 的使用者可自訂運算子多載的特性在 Java 里是不支援的。唯一在 Java 里可以重載的運算子是 "`+`"和"`+=`" 運算子，在字串里多載為連接字串.
* Java 的標準應用程序介面支援反射和動態載入任意代碼.
* C++ 支援靜態和動態的庫連接.
* Java 支援泛型，其主要目的是提供類型安全的容器. C++ 支援模板，在泛型程式設計方面提供了更強的支援.
* Java 和 C++ 都對基本類型（也叫 "內建" 類型）和使用者自訂類型（也叫 "複合" 類型）. 在 Java 里，基本類型只有值的語義，複合類型只有參照的語義。在 C++ 里所有的值都有值語義，可以建立對於任何類型的參照，這樣就允許通過參照語義來操作物件.
* C++ 支援任意類型的多重繼承。在 Java 里一個類只能從單個的類繼承而來，但一個類可以實現多個的介面（換句話說，它支援類型的多重繼承，但對於實現只能單繼承 (it supports multiple inheritance of types, but only single inheritance of implementation)）。
* Java 對於類和介面是顯式區分的。在 C++ 里多重繼承和純虛函式使得定義出類似於 Java 的介面的類是可能的，不過會有少許區別.
* Java 在語言和標準庫都對多執行緒有良好的支援. `synchronized` 這個 Java 的關鍵字為了支援多執行緒應用提供了簡單而安全的的互斥鎖，但同步 (synchronized) 區只能用 LIFO 的順序離開. Java 也為更高階的多執行緒同步提供了健壯而複雜的庫。在 C++ 里沒有專門為多執行緒定義的記憶體模型；但第三方庫提供了和 Java 差不多的功能；不過這些 C++ 庫之間差異較大，一致性不好.
* C++ 方法可以聲明為虛函式，虛函式是在運行期根據物件的類型才確定的. C++ 方法預設情況下不是虛的。在 Java 里，方法預設情況下是虛的，但可以使用 `final` 關鍵字使之聲明為非虛的.
* C++ 枚舉屬於基本類型，支援和其他整數類型之間的轉換和比較. Java 枚舉實際上是類的實例（它們從 `java.lang.Enum<E>` 擴展而來），象其他的類一樣可以定義構造函式，數據成員及方法.

### 資源管理

* Java 提供了自動化的垃圾搜集。在 C++ 里記憶體管理通常通過構造函式，析構函式以及智慧型指針。C++ 標準允許垃圾搜集，但並不強制要求；實際使用上垃圾搜集極少被用到。強制使用自動垃圾搜集導致了在 Java 里編寫實時軟體是困難的.
* C++ 可以申請任意的記憶體塊.Java 只能通過物件實例化來申請記憶體. （注意：在 Java 里，程序員可以通過建立一個位元組數組類比申請任意的記憶體塊。不過 Java 數組仍然是物件.）
* Java 和 C++ 在資源管理上使用不同的習語. Java 主要依賴只能回收記憶體的垃圾搜集機制，因為該機制如果用於回收使用中的非記憶體的系統資源可能是非常危險的。而 C++ 主要依賴 RAII (資源的取得就是初始化). 這反映了這兩種語言的幾方面的不同：
  * 在 C++ 里在棧里申請複合類型的物件是很平常的，一旦登出棧的範圍就會被銷毀。在 Java 里複合類型的物件總是在堆里申請的記憶體，而後被垃圾搜集器搜集（除非在虛擬機裡使用了逃逸分析技術來將堆的記憶體申請轉成棧的.
  * C++ 有析構函式，而 Java 有 finalizer (finalizer). 兩者都會在物件釋放之前被調用，但是它們有顯著的不同。一個 C++ 物件的析構函式必須被隱式（棧變數物件的情況）或者顯式地調用來釋放物件。析構函式在物件釋放之前同步地執行。同步，協調的反初始化以及釋放在 C++ 里滿足 RAII 的要求。在 Java 里，物件的釋放是被垃圾搜集器隱式處理的。一個 Java 物件的 finalizer 在它被最後一次存取之後和在實際釋放之前的某個時間點被異步（非同步）地調用，這個調用有可能一直不產生。非常少的物件需要 finalizer；只有那些在釋放前必須保證一些清理工作一定要做的物件來說才是需要的 — 典型的情況是：釋放對 JVM 來說是外部的資源。在 Java 里，企圖安全同步的釋放某些系統資源，只能用顯式的 try/finally 結構來進行.
  * 在 C++ 里是有可能有一個迷途指針的 – 過時的對一個已釋放的物件的參照（參照）；試圖使用一個迷途指針的結果是導致程序錯誤。在 Java 里，垃圾搜集器不會銷毀一個正在被參照的物件.
  * 在 C++ 里未初始化過的基本類型物件是有可能存在的，Java 強制要做預設初始化.
  * 在 C++ 里有可能申請了一個物件，但對它沒有任何參照。這樣的不可達物件（不可存取記憶體）是無法被銷毀的，導致了記憶體泄漏。作為對比，在 Java 里一個物件不會被回收直到它變得不可達（對於使用者程序來說）. （注意: _弱參照_(弱參照）是被支援的，這個特性讓 Java 的垃圾搜集器能夠識別不同 程度的可達性.）在 Java 里垃圾搜集阻止了很多記憶體泄漏的情況，但某些情況下泄漏仍然是可能的.
  * Java 更容易泄漏非記憶體資源，而 C++ 的慣用做法更不會導致這種泄漏.

### 庫

* C++ 對於許多平台相關的特性提供了跨平台的存取方式。從 Java 到原生的作業系統和硬體相關的函式的直接存取需要用到 JNI (Java 本地介面).

### 運行時

* C++ 通常來說會直接被編譯成機器碼，被作業系統直接執行. Java 通常會被編譯成位元組碼，被 Java 虛擬機器和直譯器或者即時編譯器編譯成機器碼然後執行.
* 因為表達方式不受限制，低階的 C++ 語言特性（例如：不被檢查的陣列存取，原始指標，類型雙關語 (type punning) 不能在編譯期間或者執行期間可靠地被檢查。相關的程式設計錯誤會導致低階的緩衝區溢位和段錯誤（記憶體區段錯誤）. 標準模板庫 提供了進階的抽象（例如 vector,list 和 map）來幫助避免這樣的錯誤。在 Java 里，低階錯誤不會發生或者會被 JVM 檢測到並以異常的形式報告給應用.
* Java 語言在越界存取陣列的時候一般來說會對陣列進行邊界檢查 (bounds checking). 這消除了導致程序不穩定的一個可能因素，但這是以執行速度更慢一些作為代價的。在一些情況下，編譯器分析 (compiler analysis) 可以檢測到不必要的邊界檢查並去掉. C++ 對於原生陣列的越界存取沒有要求特定的處理，所以需要對於原生陣列確認不越界。但 C++ 標準庫里的一部分庫象 std::vector 也提供了可選的邊界檢查。總的來說，Java 陣列是 "總是安全；嚴格限制；開銷較多" ，而 C++ 原生陣列是 "可選的開銷； 完全不限制；有潛在的不安全."

### 模板 vs. 泛型

C++ 和 Java 都提供泛型程式設計的能力，分別是模板 和 泛型 (Generics in Java). 雖然它們被創造用來解決類似的問題，有類似的語法，但實際上很不一樣.

　
:

### 雜項

* Java 和 C++ 在使代碼在不同的檔案分開方面使用了不同的技術. Java 使用了一個包系統，這個系統對所有的程序都要指定了檔名和路徑。在 Java 里，編譯器負責匯入可執行的類檔案. C++ 使用了標頭檔原始碼的包含系統來在不同的檔案分享聲明.
* 編譯好的 Java 代碼一般來說比 C++ 檔案小，因為 Java 位元組碼 (Java bytecode) 一般來說比機器碼要更緊湊，Java 程序都不是靜態連結的.
* C++ 編譯多了一個文字預處理過程，Java 是沒有的。因此一些使用者在他們的編譯過程之前增加了一個預處理的過程，這樣能更好的支援需要條件編譯的情況.
* 兩個語言裡陣列都是定長的。在 Java 里，陣列是頭等物件，而在 C++ 里它們只是它們的基本類型元素的連續的序列，經常用一個指向第一個元素的指標和一個可選的長度來參照。在 Java 里，陣列是被邊界檢查的，而且知道它們的長度，而在 C++ 里你可以將任意的序列當成一個陣列. C++ 和 Java 都提供了相關的容器類（分別為 **std::vector** 和 **java.util.ArrayList**），可以改變大小.
* Java 的除法和模除運算子是定義成零截斷的. C++ 沒有定義這兩個運算子是零截斷的還是 "負無窮截斷" 的。在 Java 里 - 3/2 總是得到 -1，但一個 C++ 編譯器可能會返回 -1 或 -2，視平台而定. C99 定義了和 Java 一樣的除法方式。兩種語言都保證對於所有的 a 和 b (b!=0)（當 a 和 b 都是整型的時候）`(a/b)*b + (a%b) == a`. C++ 版本有時候會更快，因為它允許直接使用處理器的截斷方式.
* 整型的長度在 Java 里是已定義好的 (int 為 32-bit, long 為 64-bit)，而在 C++ 里整型和指標的長度是和編譯器以及應用二進位介面相關的。因此仔細編寫的 C++ 代碼可以利用 64 位元處理器的能力而又可以在 32 位元處理器上工作。但是需要很仔細的用可移植的方式編寫。作為對比，Java 的固定整型大小使得程式設計師無法做到這樣，沒辦法利用處理器的字長會導致 Java 在 64 位元處理器上表現較差.

## 效能

想執行一個編譯好的 Java 程序，電腦上要執行 JVM；而編譯好的 C++ 程序不需要額外的應用。比較早期的 Java 版本在效能上比靜態編譯的語言如 C++ 差得很多，這是因為用 C++ 是直接編譯成一些機器指令，而當 Java 編譯成位元組碼以後用 JVM 解釋執行的時候又牽涉了不少額外的機器指令。
例如：

C++ 在大部分的情況下都比 Java 要快，
有幾個數值方面的基準測試的研究爭辯說 Java 在某些情況下可能會比 C++ 的效能好得多。 但有人說數值方面的基準測試對於語言的評估是不合適的，因為編譯器都可以做相關的最佳化，甚至可能將被測試的代碼徹底刪除。 如果涉及到一個真正現實應用的程序，Java 會因為很多原因導致效能變差：

* 所有的物件都在堆里被申請。對於使用小物件的函式來說會導致很大的效能損失，因為在棧里申請記憶體幾乎沒有效能損失。
* 方法預設是虛的。這對於小物件來說會因為虛表增加好幾倍的記憶體使用。它也會引起效能損失，因為 JIT 編譯器不得不對查虛表的過程做額外的最佳化。
* 即使使用標準的容器依然會有很多的類型轉換，這會引起效能損失，因為需要遍歷整個繼承樹。
* 虛擬機器更進一步增加了記憶體的使用，因此降低了記憶體的局部性，增加了快取命中失敗率，從而導致整個程序變慢。
* 缺乏低階細節的操作方式使得開發者無法將程序進一步最佳化，因為編譯器不支援。

有人爭論說，和 Java 相比 C++ 也有很多劣勢：

* 指標使得最佳化變得困難，因為它們可能指向任意的資料。當然現在這一點也並非完全正確，因為一些現代的編譯器引入了 "嚴格別名" 的規則 並且支援 C99 的關鍵字 _restrict_，從而嚴格限制了指標的使用，使其只能用於指向已知的變數
* Java 的垃圾搜集和使用 _malloc_/_new_ 來申請記憶體相比能擁有更好的快取連貫性，因為它的申請一般來說是順序的。然而，始終有爭論認為二者同樣會導致記憶體的「零碎化」（即多次分配和回收之後記憶體空間會變得不連續），且並沒有哪一個比對方有更明顯的快取優勢。
* 執行時編譯可能可以更好的最佳化代碼，因為可以利用執行時的額外的資訊，例如知道代碼是在什麼樣的處理器上執行。然而當今的情況也並非完全如此，因為目前最先進的 C++ 編譯器也會針對不同系統生成不同的目的碼，以期充分利用該系統的計算能力

此外，有爭議的是，花在更複雜的 C++ 代碼上的 debug 時間太多，用 Java 開發完全可以把這些時間用來最佳化 Java 代碼。當然對於一個給定的程序來說兩種語言能最佳化到什麼程度也是一方面。最後，對於處理器負擔很重的情況，例如影片彩現，C++ 能直接存取硬體，在同樣一個硬體規格下 C++ 總是會比 Java 的表現好很多。

## 所有權控制

C++ 不是任何一個公司或者組織的商標，不被任何個人擁有。
Java 原是 Sun 的商標，現在由甲骨文公司擁有。

C++ 語言由 _ISO/IEC 14882_ 定義，是一個 ISO 標準，由 _ISO/IEC JTC1/SC22/WG21_ 委員會發布。
Java 語言由 _Java Language Specification_ 定義，這是一本 Sun 公司（已被甲骨文收購）出版的書。

## 其他

兩者的物件存取格式也不一樣。

## 參考文獻

## 外部連結

* Java and C++ Memory Management — 一本詳盡的關於物件導向記憶體管理方面的出版物，在記憶體模型上對 Java 和 C++ 做了比較.
* How Java Differs from C — excerpt from Java in a Nutshell by David Flanagan
* Java vs. C++ resource management comparison - 一份有例子的綜合論文
* Java vs C performance... again... - 深入討論 Java 和 C++ 效能方面的差別