?? chapter4.htm
字號:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Thinking in Java | Chinese Version by Trans Bot</title>
<meta name="Microsoft Theme" content="inmotion 111, default"></head>
<body background="../_themes/inmotion/inmtextb.gif" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/_themes/inmotion/inmtextb.gif" bgcolor="#FFFFCC" text="#000000" link="#800000" vlink="#996633" alink="#FF3399">
<p>第4章 初始化和清除<br>
<br>
“隨著計算機的進步,‘不安全’的程序設計已成為造成編程代價高昂的罪魁禍首之一。”<br>
<br>
“初始化”和“清除”是這些安全問題的其中兩個。許多C程序的錯誤都是由于程序員忘記初始化一個變量造成的。對于現成的庫,若用戶不知道如何初始化庫的一個組件,就往往會出現這一類的錯誤。清除是另一個特殊的問題,因為用完一個元素后,由于不再關心,所以很容易把它忘記。這樣一來,那個元素占用的資源會一直保留下去,極易產生資源(主要是內存)用盡的后果。<br>
C++為我們引入了“構建器”的概念。這是一種特殊的方法,在一個對象創建之后自動調用。Java也沿用了這個概念,但新增了自己的“垃圾收集器”,能在資源不再需要的時候自動釋放它們。本章將討論初始化和清除的問題,以及Java如何提供它們的支持。<br>
<br>
4.1 用構建器自動初始化<br>
對于方法的創建,可將其想象成為自己寫的每個類都調用一次initialize()。這個名字提醒我們在使用對象之前,應首先進行這樣的調用。但不幸的是,這也意味著用戶必須記住調用方法。在Java中,由于提供了名為“構建器”的一種特殊方法,所以類的設計者可擔保每個對象都會得到正確的初始化。若某個類有一個構建器,那么在創建對象時,Java會自動調用那個構建器——甚至在用戶毫不知覺的情況下。所以說這是可以擔保的!<br>
接著的一個問題是如何命名這個方法。存在兩方面的問題。第一個是我們使用的任何名字都可能與打算為某個類成員使用的名字沖突。第二是由于編譯器的責任是調用構建器,所以它必須知道要調用是哪個方法。C++采取的方案看來是最簡單的,且更有邏輯性,所以也在Java里得到了應用:構建器的名字與類名相同。這樣一來,可保證象這樣的一個方法會在初始化期間自動調用。<br>
下面是帶有構建器的一個簡單的類(若執行這個程序有問題,請參考第3章的“賦值”小節)。<br>
<br>
148-149頁程序<br>
<br>
現在,一旦創建一個對象:<br>
new Rock();<br>
就會分配相應的存儲空間,并調用構建器。這樣可保證在我們經手之前,對象得到正確的初始化。<br>
請注意所有方法首字母小寫的編碼規則并不適用于構建器。這是由于構建器的名字必須與類名完全相同!<br>
和其他任何方法一樣,構建器也能使用自變量,以便我們指定對象的具體創建方式。可非常方便地改動上述例子,以便構建器使用自己的自變量。如下所示:<br>
<br>
149頁中程序<br>
<br>
利用構建器的自變量,我們可為一個對象的初始化設定相應的參數。舉個例子來說,假設類Tree有一個構建器,它用一個整數自變量標記樹的高度,那么就可以象下面這樣創建一個Tree對象:<br>
<br>
tree t = new Tree(12); // 12英尺高的樹<br>
<br>
若Tree(int)是我們唯一的構建器,那么編譯器不會允許我們以其他任何方式創建一個Tree對象。<br>
構建器有助于消除大量涉及類的問題,并使代碼更易閱讀。例如在前述的代碼段中,我們并未看到對initialize()方法的明確調用——那些方法在概念上獨立于定義內容。在Java中,定義和初始化屬于統一的概念——兩者缺一不可。<br>
構建器屬于一種較特殊的方法類型,因為它沒有返回值。這與void返回值存在著明顯的區別。對于void返回值,盡管方法本身不會自動返回什么,但仍然可以讓它返回另一些東西。構建器則不同,它不僅什么也不會自動返回,而且根本不能有任何選擇。若存在一個返回值,而且假設我們可以自行選擇返回內容,那么編譯器多少要知道如何對那個返回值作什么樣的處理。<br>
<br>
4.2 方法過載<br>
在任何程序設計語言中,一項重要的特性就是名字的運用。我們創建一個對象時,會分配到一個保存區域的名字。方法名代表的是一種具體的行動。通過用名字描述自己的系統,可使自己的程序更易人們理解和修改。它非常象寫散文——目的是與讀者溝通。<br>
我們用名字引用或描述所有對象與方法。若名字選得好,可使自己及其他人更易理解自己的代碼。<br>
將人類語言中存在細致差別的概念“映射”到一種程序設計語言中時,會出現一些特殊的問題。在日常生活中,我們用相同的詞表達多種不同的含義——即詞的“過載”。我們說“洗襯衫”、“洗車”以及“洗狗”。但若強制象下面這樣說,就顯得很愚蠢:“襯衫洗
襯衫”、“車洗 車”以及“狗洗
狗”。這是由于聽眾根本不需要對執行的行動作任何明確的區分。人類的大多數語言都具有很強的“冗余”性,所以即使漏掉了幾個詞,仍然可以推斷出含義。我們不需要獨一無二的標識符——可從具體的語境中推論出含義。<br>
大多數程序設計語言(特別是C)要求我們為每個函數都設定一個獨一無二的標識符。所以絕對不能用一個名為print()的函數來顯示整數,再用另一個print()顯示浮點數——每個函數都要求具備唯一的名字。<br>
在Java里,另一項因素強迫方法名出現過載情況:構建器。由于構建器的名字由類名決定,所以只能有一個構建器名稱。但假若我們想用多種方式創建一個對象呢?例如,假設我們想創建一個類,令其用標準方式進行初始化,另外從文件里讀取信息來初始化。此時,我們需要兩個構建器,一個沒有自變量(默認構建器),另一個將字串作為自變量——用于初始化對象的那個文件的名字。由于都是構建器,所以它們必須有相同的名字,亦即類名。所以為了讓相同的方法名伴隨不同的自變量類型使用,“方法過載”是非常關鍵的一項措施。同時,盡管方法過載是構建器必需的,但它亦可應用于其他任何方法,且用法非常方便。<br>
在下面這個例子里,我們向大家同時展示了過載構建器和過載的原始方法:<br>
<br>
151-152頁程序<br>
<br>
Tree既可創建成一顆種子,不含任何自變量;亦可創建成生長在苗圃中的植物。為支持這種創建,共使用了兩個構建器,一個沒有自變量(我們把沒有自變量的構建器稱作“默認構建器”,注釋①),另一個采用現成的高度。<br>
<br>
①:在Sun公司出版的一些Java資料中,用簡陋但很說明問題的詞語稱呼這類構建器——“無參數構建器”(no-arg
constructors)。但“默認構建器”這個稱呼已使用了許多年,所以我選擇了它。<br>
<br>
我們也有可能希望通過多種途徑調用info()方法。例如,假設我們有一條額外的消息想顯示出來,就使用String自變量;而假設沒有其他話可說,就不使用。由于為顯然相同的概念賦予了兩個獨立的名字,所以看起來可能有些古怪。幸運的是,方法過載允許我們為兩者使用相同的名字。<br>
<br>
4.2.1 區分過載方法<br>
若方法有同樣的名字,Java怎樣知道我們指的哪一個方法呢?這里有一個簡單的規則:每個過載的方法都必須采取獨一無二的自變量類型列表。<br>
若稍微思考幾秒鐘,就會想到這樣一個問題:除根據自變量的類型,程序員如何區分兩個同名方法的差異呢?<br>
即使自變量的順序也足夠我們區分兩個方法(盡管我們通常不愿意采用這種方法,因為它會產生難以維護的代碼):<br>
<br>
152-153頁程序<br>
<br>
兩個print()方法有完全一致的自變量,但順序不同,可據此區分它們。<br>
<br>
4.2.2 主類型的過載<br>
主(數據)類型能從一個“較小”的類型自動轉變成一個“較大”的類型。涉及過載問題時,這會稍微造成一些混亂。下面這個例子揭示了將主類型傳遞給過載的方法時發生的情況:<br>
<br>
153-155頁程序<br>
<br>
若觀察這個程序的輸出,就會發現常數值5被當作一個int值處理。所以假若可以使用一個過載的方法,就能獲取它使用的int值。在其他所有情況下,若我們的數據類型“小于”方法中使用的自變量,就會對那種數據類型進行“轉型”處理。char獲得的效果稍有些不同,這是由于假期它沒有發現一個準確的char匹配,就會轉型為int。<br>
若我們的自變量“大于”過載方法期望的自變量,這時又會出現什么情況呢?對前述程序的一個修改揭示出了答案:<br>
<br>
155-157頁程序<br>
<br>
在這里,方法采用了容量更小、范圍更窄的主類型值。若我們的自變量范圍比它寬,就必須用括號中的類型名將其轉為適當的類型。如果不這樣做,編譯器會報告出錯。<br>
大家可注意到這是一種“縮小轉換”。也就是說,在造型或轉型過程中可能丟失一些信息。這正是編譯器強迫我們明確定義的原因——我們需明確表達想要轉型的愿望。<br>
<br>
4.2.3 返回值過載<br>
我們很易對下面這些問題感到迷惑:為什么只有類名和方法自變量列出?為什么不根據返回值對方法加以區分?比如對下面這兩個方法來說,雖然它們有同樣的名字和自變量,但其實是很容易區分的:<br>
void f() {}<br>
int f() {}<br>
若編譯器可根據上下文(語境)明確判斷出含義,比如在int x=f()中,那么這樣做完全沒有問題。然而,我們也可能調用一個方法,同時忽略返回值;我們通常把這稱為“為它的副作用去調用一個方法”,因為我們關心的不是返回值,而是方法調用的其他效果。所以假如我們象下面這樣調用方法:<br>
f();<br>
Java怎樣判斷f()的具體調用方式呢?而且別人如何識別并理解代碼呢?由于存在這一類的問題,所以不能根據返回值類型來區分過載的方法。<br>
<br>
4.2.4 默認構建器<br>
正如早先指出的那樣,默認構建器是沒有自變量的。它們的作用是創建一個“空對象”。若創建一個沒有構建器的類,則編譯程序會幫我們自動創建一個默認構建器。例如:<br>
<br>
158頁上程序<br>
<br>
對于下面這一行:<br>
new Bird();<br>
它的作用是新建一個對象,并調用默認構建器——即使尚未明確定義一個象這樣的構建器。若沒有它,就沒有方法可以調用,無法構建我們的對象。然而,如果已經定義了一個構建器(無論是否有自變量),編譯程序都不會幫我們自動合成一個:<br>
<br>
class Bush {<br>
Bush(int i) {}<br>
Bush(double d) {}<br>
}<br>
<br>
現在,假若使用下述代碼:<br>
new Bush();<br>
編譯程序就會報告自己找不到一個相符的構建器。就好象我們沒有設置任何構建器,編譯程序會說:“你看來似乎需要一個構建器,所以讓我們給你制造一個吧。”但假如我們寫了一個構建器,編譯程序就會說:“啊,你已寫了一個構建器,所以我知道你想干什么;如果你不放置一個默認的,是由于你打算省略它。”<br>
<br>
4.2.5 this關鍵字<br>
如果有兩個同類型的對象,分別叫作a和b,那么您也許不知道如何為這兩個對象同時調用一個f()方法:<br>
<br>
class Banana { void f(int i) { /* ... */ } }<br>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -