Java內置多線程支持。你可以通過繼承Thread類來創建一個新的線程(重寫run()方法)。互斥發生在使用synchronized關鍵字作為類型修飾符修飾方法的對象級別。在任一時刻,只能有一個線程訪問特定對象的synchronized方法。換句話說,當進入一個synchronized方法時,首先會去對對象“上鎖”,這對使用該對象的其他synchronized方法也有作用,並且當退出方法時“解鎖”對象。沒有顯式的鎖;上鎖和解鎖都是自動的。你依舊需要自己通過創建你自己的“監控者”類實現更加復雜的線程同步。遞歸的同步方法工作正常。時間片分配在相同優先級的線程之間得不到保證。
不同於C++使用控制塊的聲明,訪問修飾符(public,private,和protected)被置於類中每個成員的定義處。沒有顯式的訪問修飾符,一個元素默認為”friendly”,可以被同一個包下的其他元素訪問(他們相當於都是C++中的友元),但是不能被包外的元素訪問。類,和類中的每個方法,都有訪問限定符去決定其是否對文件外可見。有時private關鍵字在Java中很少使用,因為“友好”訪問權限比把同一個包下的其他類的訪問排除在外通常更有用。(然而,對於多線程,private的正確使用是非常必要的。)Java中protected關鍵字意為“對於繼承者和同一個包下的類可訪問。”這與C++中的protected不同,C++中含義為“只允許繼承者訪問”(private protected用來實現一樣的效果,但是這對關鍵字的使用被取消了)。
嵌套類。在C++中,嵌套一個類目的在於名稱隱藏和代碼組織(但是C++的名稱空間消除了名稱隱藏的需求)。Java包機制提供了類似於名稱空間的效果,所以名稱隱藏對於Java而言不算是個問題。Java 1.1的內部類看起來如同內嵌類一樣。然而,一個內部類的對象私藏著參與了內部類對象創建的外部類對象的引用。這意味著內部類對象可以無限制的訪問外部類對象的成員,好像那些成員直接屬於內部類一般。這提供了對於回調問題更加簡潔的解決方案,在C++中使用指針來解決該問題。因為上一點所介紹的內部類,Java中沒有指向成員的指針。沒有內聯方法。Java編譯器可以自己決定內聯一個方法,但是你對此沒有太多控制力。你可以使用final關鍵字建議內聯一個方法。然而,inline函數也只是對C++編譯器的建議。Java的繼承與C++有一樣的作用,但是具體的語法有所不同。Java使用extends關鍵字來從基類進行繼承,使用super關鍵字去調用基類中與子類同名的方法(然而,Java中super關鍵字只允許去訪問直接父類中的方法,只能向上追溯一級。C++中則允許訪問更深層的基類方法。)基類的構造器同樣使用super關鍵字調用。正如之前提到的,所有的類最終均繼承自Object類。Java中不像C++,沒有顯式的構造初始化列表,但是Java編譯器強制你去在構造器方法中首先初始化基類,編譯器不允許你之後再對基類進行初始化。Java中通過結合自動初始化與未初始化對象引用異常來保證成員的初始化。
Java中的繼承並不改變基類中成員的保護級別。Java中和C++不同,你無法指定public,private,或者protected繼承。同樣,重寫基類的方法也無法減少基類方法的訪問權限。例如,如果基類中的方法是public的,如果你重寫了該方法,那麼你所重寫的方法訪問權限也必須是public的(編譯器會對此進行檢查)。
Java提供interface關鍵字,來創建一個只包含抽象方法並且沒有數據成員的類似於抽象基類的類。這一機制將僅被設計用來作為接口與通過extends關鍵字拓展現有功能兩者做出了清晰的劃分。值得一提的是,abstract關鍵字會產生類似的效果,不能創建該類的對象。一個abstract類可以包括抽象方法(雖然不要求必須包含),但是它也可以包含實現,這使其受限於單繼承。使用接口這一機制,防止了對於像C++中虛擬基類的需求。
使用implements關鍵字來創建可被實例化的interface版本,其語法看起來和繼承相像
Java中沒有virtual關鍵字,因為Java中的所有非靜態方法均使用動態綁定。因此,Java程序員無需決定是否采用動態綁定。C++中存在virtual關鍵字是因為這樣你就可以在進行性能調校時不使用它以獲得輕微的性能提升(或者,換句話說,“如果你不用它,你就能夠免受其累”),這通常會造成混淆與意外。final關鍵字為性能優化提供了一些余地 – 它讓編譯器知道所修飾的方法不能被重寫,也就因此其可能被靜態綁定(將其內聯,因此用起來相當於C++的非虛方法調用)。這些優化取決於編譯器。
Java不支持多繼承(MI),至少和C++不在相同的意義上(支持)。如同protected,MI似乎挺好的,但是你知道只有當你面對某個設計問題時,你才需要它。因為Java使用單根繼承,你將很少遇到需要MI的情況。interface關鍵字負責(讓你可以)結合(使用)多接口。
運行時類型檢查功能同C++中的很相像。
和C中強制類型轉換的樣子一樣。編譯器無需額外的語法,會自動調用動態轉換機制。雖然這沒有C++中“new casts”的自解釋好處,但是Java會檢查其使用情況並適時拋出異常,不會同C++中一般有錯誤的轉換。
Java中由於沒有析構函數,其異常處理與C++有所不同。可以添加finally子句塊去強制執行必須的清理語句。Java中所有的異常類型都是Throwable類的子類,這種情況保證你能夠有一個通用接口。
Java的異常規范同C++相比有很大優勢。不像C++的方式,在運行時調用函數,當錯誤發生異常被拋出,Java異常規范在編譯時被核查並執行。除此之外,重寫後的方法必須遵循異常規范:同基類中被重寫的方法相比,重寫的方法可以拋出與被重寫方法相同類型或是其子類型的異常。這提供了更為健壯的異常處理代碼。
Java有方法重載,但是沒有操作符重載。String類使用+和+=操作符來連接字符串,並且String表達式使用自動的類型轉換,但這只是特殊的內置情況。
C++中的const問題在Java中很自然的就避免了。你只能向對象傳遞引用,並且不會為你自動生成本地副本。如果你想要同C++一樣的傳值,你可以調用clone()去生成參數的本地副本(雖然clone()機制設計的有些不好 – 見第12章)。
Java中不會自動調用拷貝構造函數。
創建一個編譯時常量,你可以這樣:
因為安全問題,編寫一個“程序”同編寫一個“applet”是有很大區別的。一個重要的問題是applet將不會讓你寫磁盤,因為這將允許一個程序從一台未知機器上進行下載而弄髒你的磁盤。這種情況隨著Java 1.1數字簽名的應用而有些改觀,(數字簽名)讓你明確地知道每一個程序的作者對你的系統所擁有的特殊權限(其中的一個可能弄髒了你的磁盤;你需要知道是哪一個和發生了什麼。)。Java 1.2也承諾了對applets的更強大支持。
由於Java在某些情況下具有很強的限制性,你可能被阻止進行重要的任務,例如直接訪問硬盤。Java通過native method來解決這一問題,使用本地方法,你可以調用另一種語言編寫的程序(目前僅支持C和C++)。這樣一來,你就可以處理針對平台的問題(以一種相對而言不可移植的方式,然而,代碼是單獨的)。Applets無法調用本地方法,只有應用程序可以。
Java對於注釋文檔提供了內置支持,所以,源代碼文件可以包含其自己的文檔,這些文檔之後會被一個程序剝離並重新格式化為HTML形式。這對於文檔維護和使用而言是一個好消息。
Java包括解決特定任務的標准庫。C++則依賴第三方非標准庫。這些任務包括(或者將很快包括):
- 網絡
- 數據庫連接(通過JDBC)
- 多線程
- 分布式對象(通過RMI和CORBA)
- Commerce
這些庫實用性和標准性的本質使得能夠更快的完成程序開發。
Java 1.1包括Java Beans標准,這可用於在可視化編程環境下創建組件。這推動了可用於所有開發商開發環境下的可視化組件(的發展)。因為你不同特定開發商設計的可視化組件相關聯,這使得組件的選擇性和實用性更好。除此之外,Java Beans的設計對於程序員而言更好理解;特定開發商的組件框架往往具有陡峭的學習曲線。
如果對Java引用的訪問失敗,就會拋出異常。這在引用被使用前不會發生;Java規范僅僅指明異常必須以某種方式被拋出。許多C++運行時系統同樣可以為壞指針拋出異常。