《Java编程思想》《Think in Java》笔记
前言
這本書(shū)不適合初學(xué)者,這本書(shū)適合已經(jīng)學(xué)過(guò)Java框架并做過(guò)一兩個(gè)項(xiàng)目的同學(xué)來(lái)看,這本書(shū)對(duì)基礎(chǔ)知識(shí)的理解非常透徹。我在看的時(shí)候常常有一種醍醐灌頂?shù)母杏X(jué),常常為“原來(lái)是這樣子的!”而激動(dòng),確實(shí)是一本非常好的書(shū)。我在看書(shū)時(shí)將書(shū)本我覺(jué)得重點(diǎn)的內(nèi)容原封不動(dòng)的摘錄下來(lái),給想看重點(diǎn)的同學(xué)看看,也為一些對(duì)某些基礎(chǔ)概念不是很懂的同學(xué)給個(gè)了解渠道。
第1章?????對(duì)象入門(mén)
1.1???抽象的進(jìn)步
?????? 所有編程語(yǔ)言的最終目的都是提供一種“抽象”方法。一種較有爭(zhēng)議的說(shuō)法是:解決問(wèn)題的復(fù)雜程度直接取決于抽象的種類及質(zhì)量。
?????? OOP允許我們根據(jù)問(wèn)題來(lái)描述問(wèn)題,而不是根據(jù)方案。
?????? “純粹”的面向?qū)ο蟪绦蛟O(shè)計(jì)方法:
?????? (1)所有東西都是對(duì)象。可將對(duì)象想象成一種新型變量;它保存著數(shù)據(jù),但可要求它對(duì)自身進(jìn)行操作。理論上講,可從要解決的問(wèn)題身上提出所有概念性的組件,然后在程序中將其表達(dá)為一個(gè)對(duì)象。
?????? (2)程序是一大堆對(duì)象的組合;通過(guò)消息傳遞,各對(duì)象知道自己該做些什么。為了向?qū)ο蟀l(fā)出請(qǐng)求,需向那個(gè)對(duì)象“發(fā)送一條消息”。更具體地講,可將消息想象為一個(gè)調(diào)用請(qǐng)求,它調(diào)用的是從屬于目標(biāo)對(duì)象的一個(gè)子例程或函數(shù)。
?????? (3)每個(gè)對(duì)象都有自己的存儲(chǔ)空間,可容納其他對(duì)象。或者說(shuō),通過(guò)封裝現(xiàn)有對(duì)象,可制作出新型對(duì)象。所以,盡管對(duì)象的概念非常簡(jiǎn)單,但在程序中卻可達(dá)到任意高的復(fù)雜程度。
?????? (4)每個(gè)對(duì)象都有一種類型。根據(jù)語(yǔ)法,每個(gè)對(duì)象都是某個(gè)“類”的一個(gè)“實(shí)例”。其中,“類”(Class)是“類型”(Type)的同義詞。一個(gè)類最重要的特征就是“能將什么消息發(fā)給它?”。
?????? (5)同一類所有對(duì)象都能接收相同的消息。這實(shí)際是別有含義的一種說(shuō)法,大家不久便能理解。由于類型為“圓”(Circle)的一個(gè)對(duì)象也屬于類型為“形狀”(Shape)的一個(gè)對(duì)象,所以一個(gè)圓完全能接收形狀消息。這意味著可讓程序代碼統(tǒng)一指揮“形狀”,令其自動(dòng)控制所有符合“形狀”描述的對(duì)象,其中自然包括“圓”。這一特性稱為對(duì)象的“可替換性”,是OOP[莫星燦1]?最重要的概念之一。
1.2 對(duì)象的接口
?????? 當(dāng)我們進(jìn)行面向?qū)ο蟮某绦蛟O(shè)計(jì)時(shí),面臨的最大一項(xiàng)挑戰(zhàn)性就是:如何在“問(wèn)題空間”(問(wèn)題實(shí)際存在的地方)的元素與“方案空間”(對(duì)實(shí)際問(wèn)題進(jìn)行建模的地方,如計(jì)算機(jī))的元素之間建立理想的“一對(duì)一”對(duì)應(yīng)或映射關(guān)系。
?????? 如何利用對(duì)象完成真正有用的工作呢?必須有一種辦法能向?qū)ο蟀l(fā)出請(qǐng)求,令其做一些實(shí)際的事情,比如完成一次交易、在屏幕上畫(huà)一些東西或者打開(kāi)一個(gè)開(kāi)關(guān)等等。每個(gè)對(duì)象僅能接受特定的請(qǐng)求。我們向?qū)ο蟀l(fā)出的請(qǐng)求是通過(guò)它的“接口”(Interface)定義的,對(duì)象的“類型”或“類”則規(guī)定了它的接口形式。“類型”與“接口”的等價(jià)或?qū)?yīng)關(guān)系是面向?qū)ο蟪绦蛟O(shè)計(jì)的基礎(chǔ)。
?????? 以電燈泡為例:
?????? 在這個(gè)例子中,類型/類的名稱是 Light,可向 Light 對(duì)象發(fā)出的請(qǐng)求包括包括打開(kāi)(on)、關(guān)閉(off)、變得更明亮(brighten)或者變得更暗淡(dim)。通過(guò)簡(jiǎn)單地聲明一個(gè)名字(lt),我們?yōu)?Light 對(duì)象創(chuàng)建了一個(gè)“句柄”[莫星燦2]?。然后用new關(guān)鍵字新建類型為 Light 的一個(gè)對(duì)象。再用等號(hào)將其賦給句柄。為了向?qū)ο蟀l(fā)送一條消息,我們列出句柄名(lt),再用一個(gè)句點(diǎn)符號(hào)(.)把它同消息名稱(on)連接起來(lái)。[莫星燦3]?從中可以看出,使用一些預(yù)先定義好的類時(shí),我們?cè)诔绦蚶锊捎玫拇a是非常簡(jiǎn)單和直觀的。
1.3 實(shí)現(xiàn)方案的隱藏
?????? 從根本上說(shuō),大致有兩方面的人員涉足面向?qū)ο蟮木幊?#xff1a;“類創(chuàng)建者”(創(chuàng)建新數(shù)據(jù)類型的人)以及“客戶程序員”[莫星燦4]?(在自己的應(yīng)用程序中采用現(xiàn)成數(shù)據(jù)類型的人)。
?????? “接口”(Interface)規(guī)定了可對(duì)一個(gè)特定的對(duì)象發(fā)出哪些請(qǐng)求。然而,必須在某個(gè)地方存在著一些代碼,以便滿足這些請(qǐng)求。這些代碼與那些隱藏起來(lái)的數(shù)據(jù)便叫作“隱藏的實(shí)現(xiàn)”。站在程式化程序編寫(xiě)(Procedural Programming)的角度,整個(gè)問(wèn)題并不顯得復(fù)雜。一種類型含有與每種可能的請(qǐng)求關(guān)聯(lián)起來(lái)的函數(shù)。一旦向?qū)ο蟀l(fā)出一個(gè)特定的請(qǐng)求,就會(huì)調(diào)用那個(gè)函數(shù)。我們通常將這個(gè)過(guò)程總結(jié)為向?qū)ο蟆鞍l(fā)送一條消息”(提出一個(gè)請(qǐng)求)。對(duì)象的職責(zé)就是決定如何對(duì)這條消息作出反應(yīng)(執(zhí)行相應(yīng)的代碼)。
?????? Java采用三個(gè)顯式(明確)關(guān)鍵字以及一個(gè)隱式(暗示)關(guān)鍵字來(lái)設(shè)置類邊界:public,private,protected 以及暗示性的friendly[莫星燦5]?。若未明確指定其他關(guān)鍵字,則默認(rèn)為后者。這些關(guān)鍵字的使用和含義都是相當(dāng)直觀的,它們決定了誰(shuí)能使用后續(xù)的定義內(nèi)容。“public”(公共)意味著后續(xù)的定義任何人均可使用。而在另一方面,“private”(私有)意味著除您自己、類型的創(chuàng)建者以及那個(gè)類型的內(nèi)部函數(shù)成員,其他任何人都不能訪問(wèn)后續(xù)的定義信息。private在您與客戶程序員之間豎起了一堵墻。若有人試圖訪問(wèn)私有成員,就會(huì)得到一個(gè)編譯期錯(cuò)誤。“friendly”(友好的)涉及“包裝”或“封裝”(Package)的概念——即Java 用來(lái)構(gòu)建庫(kù)的方法。若某樣?xùn)|西是“友好的”,意味著它只能在這個(gè)包裝的范圍內(nèi)使用(所以這一訪問(wèn)級(jí)別有時(shí)也叫作“包裝訪問(wèn)”)。“protected”(受保護(hù)的)與“private”相似,只是一個(gè)繼承的類可訪問(wèn)受保護(hù)的成員,但不能訪問(wèn)私有成員。繼承的問(wèn)題不久就要談到。
1.4 方案的重復(fù)使用
?????? 許多人認(rèn)為代碼或設(shè)計(jì)方案的重復(fù)使用是面向?qū)ο蟮某绦蛟O(shè)計(jì)提供的最偉大的一種杠桿。
?????? 為重復(fù)使用一個(gè)類,最簡(jiǎn)單的辦法是僅直接使用那個(gè)類的對(duì)象。但同時(shí)也能將那個(gè)類的一個(gè)對(duì)象置入一個(gè)新類。我們把這叫作“創(chuàng)建一個(gè)成員對(duì)象”。新類可由任意數(shù)量和類型的其他對(duì)象構(gòu)成。無(wú)論如何,只要新類達(dá)到了設(shè)計(jì)要求即可。這個(gè)概念叫作“組織”——在現(xiàn)有類的基礎(chǔ)上組織一個(gè)新類。有時(shí),我們也將組織稱作“包含”關(guān)系,比如“一輛車包含了一個(gè)變速箱”。
[莫星燦6]?1.5 繼承:重新使用接口
?????? 使用繼承時(shí),相當(dāng)于創(chuàng)建了一個(gè)新類。這個(gè)新類不僅包含了現(xiàn)有類型的所有成員(盡管private 成員被隱藏起來(lái),且不能訪問(wèn)),但更重要的是,它復(fù)制了基礎(chǔ)類的接口。也就是說(shuō),可向基礎(chǔ)類的對(duì)象發(fā)送的所有消息亦可原樣發(fā)給衍生類的對(duì)象。根據(jù)可以發(fā)送的消息,我們能知道類的類型。這意味著衍生類具有與基礎(chǔ)類相同的類型!為真正理解面向?qū)ο蟪绦蛟O(shè)計(jì)的含義,首先必須認(rèn)識(shí)到這種類型的等價(jià)關(guān)系。
1.5.1 改善基礎(chǔ)類
?????? 為改善一個(gè)函數(shù),只需為衍生類的函數(shù)建立一個(gè)新定義即可。我們的目標(biāo)是:“盡管使用的函數(shù)接口未變,但它的新版本具有不同的表現(xiàn)”。
1.5.2 等價(jià)與類似關(guān)系
?????? 但在許多時(shí)候,我們必須為衍生類型加入新的接口元素。所以不僅擴(kuò)展了接口,也創(chuàng)建了一種新類型。這種新類型仍可替換成基礎(chǔ)類型,但這種替換并不是完美的,因?yàn)椴豢稍诨A(chǔ)類里訪問(wèn)新函數(shù)。我們將其稱作“類似”關(guān)系;新類型擁有舊類型的接口,但也包含了其他函數(shù),所以不能說(shuō)它們是完全等價(jià)的。
1.6 多形對(duì)象的互換使用
?????? 通常,繼承最終會(huì)以創(chuàng)建一系列類收?qǐng)?#xff0c;所有類都建立在統(tǒng)一的接口基礎(chǔ)上。我們用一幅顛倒的樹(shù)形圖來(lái)闡明這一點(diǎn)(注釋⑤):
⑤:這兒采用了“統(tǒng)一記號(hào)法”,本書(shū)將主要采用這種方法。
?????? 對(duì)這樣的一系列類,我們要進(jìn)行的一項(xiàng)重要處理就是將衍生類的對(duì)象當(dāng)作基礎(chǔ)類的一個(gè)對(duì)象對(duì)待。這一點(diǎn)是非常重要的,因?yàn)樗馕吨覀冎恍杈帉?xiě)單一的代碼,令其忽略類型的特定細(xì)節(jié),只與基礎(chǔ)類打交道。這樣一來(lái),那些代碼就可與類型信息分開(kāi)。所以更易編寫(xiě),也更易理解。此外,若通過(guò)繼承增添了一種新類型,如“三角形”,那么我們?yōu)椤皫缀涡螤睢毙骂愋途帉?xiě)的代碼會(huì)象在舊類型里一樣良好地工作。所以說(shuō)程序具備了“擴(kuò)展能力”,具有“擴(kuò)展性”。
?????? 以上面的例子為基礎(chǔ),假設(shè)我們用 Java 寫(xiě)了這樣一個(gè)函數(shù):
| Void doStuff(Shape s){ ? s.erase(); ? //… ? s.draw(); } |
?????? 這個(gè)函數(shù)可與任何“幾何形狀”(Shape)通信,所以完全獨(dú)立于它要描繪(draw)和刪除(erase)的任何特定類型的對(duì)象。如果我們?cè)谄渌恍┏绦蚶锸褂?doStuff()函數(shù):
?????? 那么對(duì)doStuff()的調(diào)用會(huì)自動(dòng)良好地工作,無(wú)論對(duì)象的具體類型是什么。
?????? 這實(shí)際是一個(gè)非常有用的編程技巧。請(qǐng)考慮下面這行代碼:
?????? doStuff(c);
?????? 此時(shí),一個(gè) Circle(圓)句柄傳遞給一個(gè)本來(lái)期待 Shape(形狀)句柄的函數(shù)。由于圓是一種幾何形狀,所以doStuff()能正確地進(jìn)行處理。也就是說(shuō),凡是 doStuff()能發(fā)給一個(gè) Shape的消息,Circle也能接收。所以這樣做是安全的,不會(huì)造成錯(cuò)誤。
?????? 我們將這種把衍生類型當(dāng)作它的基本類型處理的過(guò)程叫作“Upcasting”(上溯造型[莫星燦7]?)。其中,“cast”(造型)是指根據(jù)一個(gè)現(xiàn)成的模型創(chuàng)建;而“Up”(向上)表明繼承的方向是從“上面”來(lái)的——即基礎(chǔ)類位于頂部,而衍生類在下方展開(kāi)。所以,根據(jù)基礎(chǔ)類進(jìn)行造型就是一個(gè)從上面繼承的過(guò)程,即“Upcasting”。在面向?qū)ο蟮某绦蚶?#xff0c;通常都要用到上溯造型技術(shù)。這是避免去調(diào)查準(zhǔn)確類型的一個(gè)好辦法。請(qǐng)看看doStuff()里的代碼:
| s.erase(); // ... s.draw(); |
?????? 注意它并未這樣表達(dá):“如果你是一個(gè)Circle,就這樣做;如果你是一個(gè)Square,就那樣做;等等”。若那樣編寫(xiě)代碼,就需檢查一個(gè)Shape 所有可能的類型,如圓、矩形等等。這顯然是非常麻煩的,而且每次添加了一種新的 Shape類型后,都要相應(yīng)地進(jìn)行修改。在這兒,我們只需說(shuō):“你是一種幾何形狀,我知道你能將自己刪掉,即erase();請(qǐng)自己采取那個(gè)行動(dòng),并自己去控制所有的細(xì)節(jié)吧。”
1.6.1 動(dòng)態(tài)綁定
?????? 將一條消息發(fā)給對(duì)象時(shí),如果并不知道對(duì)方的具體類型是什么,但采取的行動(dòng)同樣是正確的,這種情況就叫作“多形性”(Polymorphism)。對(duì)面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言來(lái)說(shuō),它們用以實(shí)現(xiàn)多形性的方法叫作“動(dòng)態(tài)綁定[莫星燦8]?”。編譯器和運(yùn)行期系統(tǒng)會(huì)負(fù)責(zé)對(duì)所有細(xì)節(jié)的控制;我們只需知道會(huì)發(fā)生什么事情,而且更重要的是,如何利用它幫助自己設(shè)計(jì)程序。
1.6.2 抽象的基礎(chǔ)類和接口
?????? 設(shè)計(jì)程序時(shí),我們經(jīng)常都希望基礎(chǔ)類只為自己的衍生類提供一個(gè)接口。也就是說(shuō),我們不想其他任何人實(shí)際創(chuàng)建基礎(chǔ)類的一個(gè)對(duì)象,只對(duì)上溯造型成它,以便使用它們的接口。[莫星燦9]?為達(dá)到這個(gè)目的,需要把那個(gè)類變成“抽象”的——使用abstract 關(guān)鍵字。若有人試圖創(chuàng)建抽象類的一個(gè)對(duì)象,編譯器就會(huì)阻止他們。這種工具可有效強(qiáng)制實(shí)行一種特殊的設(shè)計(jì)。
?????? 亦可用abstract 關(guān)鍵字描述一個(gè)尚未實(shí)現(xiàn)的方法——作為一個(gè)“根”使用,指出:“這是適用于從這個(gè)類繼承的所有類型的一個(gè)接口函數(shù),但目前尚沒(méi)有對(duì)它進(jìn)行任何形式的實(shí)現(xiàn)。”抽象方法也許只能在一個(gè)抽象類里創(chuàng)建。繼承了一個(gè)類后,那個(gè)方法就必須實(shí)現(xiàn),否則繼承的類也會(huì)變成“抽象”類。通過(guò)創(chuàng)建一個(gè)抽象方法,我們可以將一個(gè)方法置入接口中,不必再為那個(gè)方法提供可能毫無(wú)意義的主體代碼。
?????? interface(接口)關(guān)鍵字將抽象類的概念更延伸了一步,它完全禁止了所有的函數(shù)定義。“接口”是一種相當(dāng)有效和常用的工具。另外如果自己愿意,亦可將多個(gè)接口都合并到一起(不能從多個(gè)普通class 或abstract class 中繼承)。
1.7 對(duì)象的創(chuàng)建和存在時(shí)間
從技術(shù)角度說(shuō),OOP(面向?qū)ο蟪绦蛟O(shè)計(jì))只是涉及抽象的數(shù)據(jù)類型、繼承以及多形性,但另一些問(wèn)題也可能顯得非常重要。本節(jié)將就這些問(wèn)題進(jìn)行探討。
最重要的問(wèn)題之一是對(duì)象的創(chuàng)建及破壞方式。對(duì)象需要的數(shù)據(jù)位于哪兒,如何控制對(duì)象的“存在時(shí)間”呢?針對(duì)這個(gè)問(wèn)題,解決的方案是各異其趣的。
第二個(gè)方法是在一個(gè)內(nèi)存池中動(dòng)態(tài)創(chuàng)建對(duì)象,該內(nèi)存池亦叫“堆”或者“內(nèi)存堆”。若采用這種方式,除非進(jìn)入運(yùn)行期,否則根本不知道到底需要多少個(gè)對(duì)象,也不知道它們的存在時(shí)間有多長(zhǎng),以及準(zhǔn)確的類型是什么。這些參數(shù)都在程序正式運(yùn)行時(shí)才決定的。若需一個(gè)新對(duì)象,只需在需要它的時(shí)候在內(nèi)存堆里簡(jiǎn)單地創(chuàng)建它即可。由于存儲(chǔ)空間的管理是運(yùn)行期間動(dòng)態(tài)進(jìn)行的,所以在內(nèi)存堆里分配存儲(chǔ)空間的時(shí)間比在堆棧里創(chuàng)建的時(shí)間長(zhǎng)得多(在堆棧里創(chuàng)建存儲(chǔ)空間一般只需要一個(gè)簡(jiǎn)單的指令,將堆棧指針向下或向下移動(dòng)即可)。由于動(dòng)態(tài)創(chuàng)建方法使對(duì)象本來(lái)就傾向于復(fù)雜,所以查找存儲(chǔ)空間以及釋放它所需的額外開(kāi)銷不會(huì)為對(duì)象的創(chuàng)建造成明顯的影響。除此以外,更大的靈活性對(duì)于常規(guī)編程問(wèn)題的解決是至關(guān)重要的。
1.7.1 集合與繼承器
針對(duì)一個(gè)特定問(wèn)題的解決,如果事先不知道需要多少個(gè)對(duì)象,或者它們的持續(xù)時(shí)間有多長(zhǎng),那么也不知道如何保存那些對(duì)象。既然如此,怎樣才能知道那些對(duì)象要求多少空間呢?事先上根本無(wú)法提前知道,除非進(jìn)入運(yùn)行期。
“繼續(xù)器”(Iterator),它屬于一種對(duì)象,負(fù)責(zé)選擇集合內(nèi)的元素,并把它們提供給繼
承器的用戶。作為一個(gè)類,它也提供了一級(jí)抽象。利用這一級(jí)抽象,可將集合細(xì)節(jié)與用于訪問(wèn)那個(gè)集合的代碼隔離開(kāi)。通過(guò)繼承器的作用,集合被抽象成一個(gè)簡(jiǎn)單的序列。繼承器允許我們遍歷那個(gè)序列,同時(shí)毋需關(guān)心基礎(chǔ)結(jié)構(gòu)是什么——換言之,不管它是一個(gè)矢量、一個(gè)鏈接列表、一個(gè)堆棧,還是其他什么東西。這樣一來(lái),我們就可以靈活地改變基礎(chǔ)數(shù)據(jù),不會(huì)對(duì)程序里的代碼造成干擾。
1.7.2 單根結(jié)構(gòu)
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,由于C++的引入而顯得尤為突出的一個(gè)問(wèn)題是:所有類最終是否都應(yīng)從單獨(dú)一個(gè)基礎(chǔ)類繼承。在Java 中(與其他幾乎所有OOP語(yǔ)言一樣),對(duì)這個(gè)問(wèn)題的答案都是肯定的,而且這個(gè)終級(jí)基礎(chǔ)類的名字很簡(jiǎn)單,就是一個(gè)“Object[莫星燦10]?”。
?????? 單根結(jié)構(gòu)中的所有對(duì)象都有一個(gè)通用接口,所以它們最終都屬于相同的類型。
?????? 單根結(jié)構(gòu)中的所有對(duì)象(比如所有 Java 對(duì)象)都可以保證擁有一些特定的功能。
?????? 利用單根結(jié)構(gòu),我們可以更方便地實(shí)現(xiàn)一個(gè)垃圾收集器。與此有關(guān)的必要支持可安裝于基礎(chǔ)類中,而垃圾收集器可將適當(dāng)?shù)南l(fā)給系統(tǒng)內(nèi)的任何對(duì)象。如果沒(méi)有這種單根結(jié)構(gòu),而且系統(tǒng)通過(guò)一個(gè)句柄來(lái)操縱對(duì)象,那么實(shí)現(xiàn)垃圾收集器的途徑會(huì)有很大的不同,而且會(huì)面臨許多障礙。
1.7.3 集合庫(kù)與方便使用集合
由于集合是我們經(jīng)常都要用到的一種工具,所以一個(gè)集合庫(kù)是十分必要的,它應(yīng)該可以方便地重復(fù)使用。這樣一來(lái),我們就可以方便地取用各種集合,將其插入自己的程序。Java 提供了這樣的一個(gè)庫(kù),盡管它在Java1.0和 1.1中都顯得非常有限。
1. 下溯造型與模板/通用性
為了使這些集合能夠重復(fù)使用,或者“再生”,Java 提供了一種通用類型,以前曾把它叫作“Object”。單根結(jié)構(gòu)意味著、所有東西歸根結(jié)底都是一個(gè)對(duì)象”!所以容納了Object 的一個(gè)集合實(shí)際可以容納任何東西。這使我們對(duì)它的重復(fù)使用變得非常簡(jiǎn)便。
為使用這樣的一個(gè)集合,只需添加指向它的對(duì)象句柄即可,以后可以通過(guò)句柄重新使用對(duì)象。但由于集合只能容納Object,所以在我們向集合里添加對(duì)象句柄時(shí),它會(huì)上溯造型成 Object,這樣便丟失了它的身份或者標(biāo)識(shí)信息。再次使用它的時(shí)候,會(huì)得到一個(gè)Object 句柄,而非指向我們?cè)缦戎萌氲哪莻€(gè)類型的句柄。所以怎樣才能歸還它的本來(lái)面貌,調(diào)用早先置入集合的那個(gè)對(duì)象的有用接口呢?
在這里,我們?cè)俅斡玫搅嗽煨?#xff08;Cast)。但這一次不是在分級(jí)結(jié)構(gòu)中上溯造型成一種更“通用”的類型。而是下溯造型成一種更“特殊”的類型。這種造型方法叫作“下溯造型[莫星燦11]?”(Downcasting)。舉個(gè)例子來(lái)說(shuō),我們知道在上溯造型的時(shí)候,Circle(圓)屬于Shape(幾何形狀)的一種類型,所以上溯造型是安全的。但我們不知道一個(gè)Object到底是 Circle 還是Shape,所以很難保證下溯造型的安全進(jìn)行,除非確切地知道自己要操作的是什么。
1.7.4 清除時(shí)的困境:由誰(shuí)負(fù)責(zé)清除?
在Java 中,垃圾收集器在設(shè)計(jì)時(shí)已考慮到了內(nèi)存的釋放問(wèn)題(盡管這并不包括清除一個(gè)對(duì)象涉及到的其他方面)。垃圾收集器“知道”一個(gè)對(duì)象在什么時(shí)候不再使用,然后會(huì)自動(dòng)釋放那個(gè)對(duì)象占據(jù)的內(nèi)存空間。采用這種方式,另外加上所有對(duì)象都從單個(gè)根類Object 繼承的事實(shí),而且由于我們只能在內(nèi)存堆中以一種方式創(chuàng)建對(duì)象,所以Java 的編程要比 C++的編程簡(jiǎn)單得多。我們只需要作出少量的抉擇,即可克服原先存在的大量障礙。
1.????垃圾收集器對(duì)效率及靈活性的影響
既然這是如此好的一種手段,為什么在C++里沒(méi)有得到充分的發(fā)揮呢?我們當(dāng)然要為這種編程的方便性付出
一定的代價(jià),代價(jià)就是運(yùn)行期的開(kāi)銷。正如早先提到的那樣,在C++中,我們可在堆棧中創(chuàng)建對(duì)象。在這種情況下,對(duì)象會(huì)得以自動(dòng)清除(但不具有在運(yùn)行期間隨心所欲創(chuàng)建對(duì)象的靈活性)。在堆棧中創(chuàng)建對(duì)象是為對(duì)象分配存儲(chǔ)空間最有效的一種方式,也是釋放那些空間最有效的一種方式。在內(nèi)存堆(Heap)中創(chuàng)建對(duì)象可能要付出昂貴得多的代價(jià)。如果總是從同一個(gè)基礎(chǔ)類繼承,并使所有函數(shù)調(diào)用都具有“同質(zhì)多形”特征,那么也不可避免地需要付出一定的代價(jià)。但垃圾收集器是一種特殊的問(wèn)題,因?yàn)槲覀?span style="color:red">永遠(yuǎn)不能確定它什么時(shí)候啟動(dòng)或者要花多長(zhǎng)的時(shí)間。這意味著在Java 程序執(zhí)行期間,存在著一種不連貫的因素。所以在某些特殊的場(chǎng)合,我們必須避免用它——比如在一個(gè)程序的執(zhí)行必須保持穩(wěn)定、連貫的時(shí)候(通常把它們叫作“實(shí)時(shí)程序”,盡管并不是所有實(shí)時(shí)編程問(wèn)題都要這方面的要求)。
1.8 違例控制:解決錯(cuò)誤
“違例控制”將錯(cuò)誤控制方案內(nèi)置到程序設(shè)計(jì)語(yǔ)言中,有時(shí)甚至內(nèi)建到操作系統(tǒng)內(nèi)。這里的“違例”(Exception)屬于一個(gè)特殊的對(duì)象,它會(huì)從產(chǎn)生錯(cuò)誤的地方“扔”或“擲”出來(lái)。隨后,這個(gè)違例會(huì)被設(shè)計(jì)用于控制特定類型錯(cuò)誤的“違例控制器”捕獲。在情況變得不對(duì)勁的時(shí)候,可能有幾個(gè)違例控制器并行捕獲對(duì)應(yīng)的違例對(duì)象。由于采用的是獨(dú)立的執(zhí)行路徑,所以不會(huì)干擾我們的常規(guī)執(zhí)行代碼。這樣便使代碼的編寫(xiě)變得更加簡(jiǎn)單,因?yàn)椴槐亟?jīng)常性強(qiáng)制檢查代碼。除此以外,“擲”出的一個(gè)違例不同于從函數(shù)返回的錯(cuò)誤值,也不同于由函數(shù)設(shè)置的一個(gè)標(biāo)志。那些錯(cuò)誤值或標(biāo)志的作用是指示一個(gè)錯(cuò)誤狀態(tài),是可以忽略的。但違例不能被忽略,所以肯定能在某個(gè)地方得到處置。最后,利用違例能夠可靠地從一個(gè)糟糕的環(huán)境中恢復(fù)。此時(shí)一般不需要退出,我們可以采取某些處理,恢復(fù)程序的正常執(zhí)行。顯然,這樣編制出來(lái)的程序顯得更加可靠。[莫星燦12]?
1.9 多線程
有些時(shí)候,中斷對(duì)那些實(shí)時(shí)性很強(qiáng)的任務(wù)來(lái)說(shuō)是很有必要的。但還存在其他許多問(wèn)題,它們只要求將問(wèn)題劃分進(jìn)入獨(dú)立運(yùn)行的程序片斷中,使整個(gè)程序能更迅速地響應(yīng)用戶的請(qǐng)求。在一個(gè)程序中,這些獨(dú)立運(yùn)行的片斷叫作“線程”(Thread),利用它編程的概念就叫作“多線程處理”。多線程處理一個(gè)常見(jiàn)的例子就是用戶界面。利用線程,用戶可按下一個(gè)按鈕,然后程序會(huì)立即作出響應(yīng),而不是讓用戶等待程序完成了當(dāng)前任務(wù)以后才開(kāi)始響應(yīng)。
最開(kāi)始,線程只是用于分配單個(gè)處理器的處理時(shí)間的一種工具。但假如操作系統(tǒng)本身支持多個(gè)處理器,那么每個(gè)線程都可分配給一個(gè)不同的處理器,真正進(jìn)入“并行運(yùn)算”狀態(tài)。從程序設(shè)計(jì)語(yǔ)言的角度看,多線程操作最有價(jià)值的特性之一就是程序員不必關(guān)心到底使用了多少個(gè)處理器。程序在邏輯意義上被分割為數(shù)個(gè)線程;假如機(jī)器本身安裝了多個(gè)處理器,那么程序會(huì)運(yùn)行得更快,毋需作出任何特殊的調(diào)校。根據(jù)前面的論述,大家可能感覺(jué)線程處理非常簡(jiǎn)單。但必須注意一個(gè)問(wèn)題:共享資源!如果有多個(gè)線程同時(shí)運(yùn)行,而且它們?cè)噲D訪問(wèn)相同的資源,就會(huì)遇到一個(gè)問(wèn)題。舉個(gè)例子來(lái)說(shuō),兩個(gè)進(jìn)程不能將信息同時(shí)發(fā)送給一臺(tái)打印機(jī)。為解決這個(gè)問(wèn)題,對(duì)那些可共享的資源來(lái)說(shuō)(比如打印機(jī)),它們?cè)谑褂闷陂g必須進(jìn)入鎖定狀態(tài)。所以一個(gè)線程可將資源鎖定,在完成了它的任務(wù)后,再解開(kāi)(釋放)這個(gè)鎖,使其他線程可以接著使用同樣的資源。
Java 的多線程機(jī)制已內(nèi)建到語(yǔ)言中,這使一個(gè)可能較復(fù)雜的問(wèn)題變得簡(jiǎn)單起來(lái)。對(duì)多線程處理的支持是在對(duì)象這一級(jí)支持的,所以一個(gè)執(zhí)行線程可表達(dá)為一個(gè)對(duì)象。Java 也提供了有限的資源鎖定方案。它能鎖定任何對(duì)象占用的內(nèi)存(內(nèi)存實(shí)際是多種共享資源的一種),所以同一時(shí)間只能有一個(gè)線程使用特定的內(nèi)存空間。為達(dá)到這個(gè)目的,需要使用synchronized關(guān)鍵字。其他類型的資源必須由程序員明確鎖定,這通常要求程序員創(chuàng)建一個(gè)對(duì)象,用它代表一把鎖,所有線程在訪問(wèn)那個(gè)資源時(shí)都必須檢查這把鎖。
1.10 永久性
Java 1.1 提供了對(duì)“有限永久性”的支持,這意味著我們可將對(duì)象簡(jiǎn)單地保存到磁盤(pán)上,以后任何時(shí)間都可取回。之所以稱它為“有限”的,是由于我們?nèi)匀恍枰鞔_發(fā)出調(diào)用,進(jìn)行對(duì)象的保存和取回工作。這些工作不能自動(dòng)進(jìn)行。在Java 未來(lái)的版本中,對(duì)“永久性”的支持有望更加全面。
1.11 Java 和因特網(wǎng)
Java 除了可解決傳統(tǒng)的程序設(shè)計(jì)問(wèn)題以外,還能解決World Wide Web(萬(wàn)維網(wǎng))上的編程問(wèn)題。
1.11.1 什么是 Web ?
1. 客戶機(jī)/服務(wù)器計(jì)算
客戶機(jī)/服務(wù)器系統(tǒng)的基本思想是我們能在一個(gè)統(tǒng)一的地方集中存放信息資源。一般將數(shù)據(jù)集中保存在某個(gè)數(shù)據(jù)庫(kù)中,根據(jù)其他人或者機(jī)器的請(qǐng)求將信息投遞給對(duì)方。客戶機(jī)/服務(wù)器概述的一個(gè)關(guān)鍵在于信息是“集中存放”的。所以我們能方便地更改信息,然后將修改過(guò)的信息發(fā)放給信息的消費(fèi)者。將各種元素集中到一起,信息倉(cāng)庫(kù)、用于投遞信息的軟件以及信息及軟件所在的那臺(tái)機(jī)器,它們聯(lián)合起來(lái)便叫作“服務(wù)器”(Server)。而對(duì)那些駐留在遠(yuǎn)程機(jī)器上的軟件,它們需要與服務(wù)器通信,取回信息,進(jìn)行適當(dāng)?shù)奶幚?#xff0c;然后在遠(yuǎn)程機(jī)器上顯示出來(lái),這些就叫作“客戶”(Client)。[莫星燦13]?
這樣看來(lái),客戶機(jī)/服務(wù)器的基本概念并不復(fù)雜。這里要注意的一個(gè)主要問(wèn)題是單個(gè)服務(wù)器需要同時(shí)向多個(gè)客戶提供服務(wù)。在這一機(jī)制中,通常少不了一套數(shù)據(jù)庫(kù)管理系統(tǒng),使設(shè)計(jì)人員能將數(shù)據(jù)布局封裝到表格中,以獲得最優(yōu)的使用。除此以外,系統(tǒng)經(jīng)常允許客戶將新信息插入一個(gè)服務(wù)器。這意味著必須確保客戶的新數(shù)據(jù)不會(huì)與其他客戶的新數(shù)據(jù)沖突,或者說(shuō)需要保證那些數(shù)據(jù)在加入數(shù)據(jù)庫(kù)的時(shí)候不會(huì)丟失(用數(shù)據(jù)庫(kù)的術(shù)語(yǔ)來(lái)說(shuō),這叫作“事務(wù)處理”)。客戶軟件發(fā)生了改變之后,它們必須在客戶機(jī)器上構(gòu)建、調(diào)試以及安裝。所有這些會(huì)使問(wèn)題變得比我們一般想象的復(fù)雜得多。另外,對(duì)多種類型的計(jì)算機(jī)和操作系統(tǒng)的支持也是一個(gè)大問(wèn)題。最后,性能的問(wèn)題顯得尤為重要:可能會(huì)有數(shù)百個(gè)客戶同時(shí)向服務(wù)器發(fā)出請(qǐng)求。所以任何微小的延誤都是不能忽視的。為盡可能緩解潛伏的問(wèn)題,程序員需要謹(jǐn)慎地分散任務(wù)的處理負(fù)擔(dān)。一般可以考慮讓客戶機(jī)負(fù)擔(dān)部分處理任務(wù),但有時(shí)亦可分派給服務(wù)器所在地的其他機(jī)器,那些機(jī)器亦叫作“中間件[莫星燦14]?”(中間件也用于改進(jìn)對(duì)系統(tǒng)的維護(hù))。
2. Web是一個(gè)巨大的服務(wù)器
Web實(shí)際就是一套規(guī)模巨大的客戶機(jī)/服務(wù)器系統(tǒng)。但它的情況要復(fù)雜一些,因?yàn)樗蟹?wù)器和客戶都同時(shí)存在于單個(gè)網(wǎng)絡(luò)上面。但我們沒(méi)必要了解更進(jìn)一步的細(xì)節(jié),因?yàn)槲ㄒ灰P(guān)心的就是一次建立同一個(gè)服務(wù)器的連接,并同它打交道(即使可能要在全世界的范圍內(nèi)搜索正確的服務(wù)器)。
最開(kāi)始的時(shí)候,這是一個(gè)簡(jiǎn)單的單向操作過(guò)程。我們向一個(gè)服務(wù)器發(fā)出請(qǐng)求,它向我們回傳一個(gè)文件,由于本機(jī)的瀏覽器軟件(亦即“客戶”或“客戶程序”)負(fù)責(zé)解釋和格式化,并在我們面前的屏幕上正確地顯示出來(lái)。但人們不久就不滿足于只從一個(gè)服務(wù)器傳遞網(wǎng)頁(yè)。他們希望獲得完全的客戶機(jī)/服務(wù)器能力,使客戶(程序)也能反饋一些信息到服務(wù)器。比如希望對(duì)服務(wù)器上的數(shù)據(jù)庫(kù)進(jìn)行檢索,向服務(wù)器添加新信息,或者下一份訂單等等(這也提供了比以前的系統(tǒng)更高的安全要求)。[莫星燦15]?在Web的發(fā)展過(guò)程中,我們可以很清晰地看出這些令人心喜的變化。
Web瀏覽器的發(fā)展終于邁出了重要的一步:某個(gè)信息可在任何類型的計(jì)算機(jī)上顯示出來(lái),毋需任何改動(dòng)。然而,瀏覽器仍然顯得很原始,在用戶迅速增多的要求面前顯得有些力不從心。它們的交互能力不夠強(qiáng),而且對(duì)服務(wù)器和因特網(wǎng)都造成了一定程度的干擾。這是由于每次采取一些要求編程的操作時(shí),必須將信息反饋回服務(wù)器,在服務(wù)器那一端進(jìn)行處理。所以完全可能需要等待數(shù)秒乃至數(shù)分鐘的時(shí)間才會(huì)發(fā)現(xiàn)自己剛才拼錯(cuò)了一個(gè)單詞。由于瀏覽器只是一個(gè)純粹的查看程序,所以連最簡(jiǎn)單的計(jì)算任務(wù)都不能進(jìn)行(當(dāng)然在另一方面,它也顯得非常安全,因?yàn)椴荒茉诒緳C(jī)上面執(zhí)行任何程序,避開(kāi)了程序錯(cuò)誤或者病毒的騷擾)。
為解決這個(gè)問(wèn)題,人們采取了許多不同的方法。最開(kāi)始的時(shí)候,人們對(duì)圖形標(biāo)準(zhǔn)進(jìn)行了改進(jìn),使瀏覽器能顯示更好的動(dòng)畫(huà)和視頻。為解決剩下的問(wèn)題,唯一的辦法就是在客戶端(瀏覽器)內(nèi)運(yùn)行程序。這就叫作“客戶端編程”,它是對(duì)傳統(tǒng)的“服務(wù)器端編程”的一個(gè)非常重要的拓展。[莫星燦16]?
?[莫星燦1]OOP: 面向?qū)ο蟪绦蛟O(shè)計(jì)
?[莫星燦2]終于知道句柄是什么鬼了!
?[莫星燦3]調(diào)用方法的原理
?[莫星燦4]不太理解
?[莫星燦5]較形象地理解這四個(gè)關(guān)鍵字
?[莫星燦6]同感
?[莫星燦7]也叫向上轉(zhuǎn)型
?[莫星燦8]跟“什么人說(shuō)什么話”道理類似
?[莫星燦9]使用抽象的原因
[莫星燦10]所有類都繼承Object
?[莫星燦11]向下轉(zhuǎn)型
?[莫星燦12]Exception Try{ }catch{ }的理解
?[莫星燦13]形象生動(dòng)的詮釋何為服務(wù)器和客戶,BC端
?[莫星燦14]大公司如阿里這種應(yīng)該對(duì)這個(gè)很有研究了
?[莫星燦15]早期瀏覽器形態(tài)
?[莫星燦16]B/S端
總結(jié)
以上是生活随笔為你收集整理的《Java编程思想》《Think in Java》笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android分享功能,微博、QQ、QQ
- 下一篇: Java面试题:在一个递增的数组里面,找