JAVA程序員必讀:基礎(chǔ)篇(2)面向?qū)ο缶幊谈拍?/p> 如果你以前從來(lái)沒(méi)有使用面向?qū)ο笳Z(yǔ)言,你需要在開(kāi)始編寫(xiě)JAVA代碼之前先理解這個(gè)概念。你需要理解什么是對(duì)象、什么是類、對(duì)象和類的關(guān)系怎樣以及使用消息怎樣在對(duì)象之間進(jìn)行通訊。本教程的前面部分將描述面向?qū)ο缶幊痰母拍睿竺娴慕坛虒⒔棠阍鯓訉⑦@個(gè)概念編成代碼。 2.1什么是對(duì)象 對(duì)象是一些相關(guān)的變量和方法的軟件集。軟件對(duì)象經(jīng)常用于模仿現(xiàn)實(shí)世界中我們身邊的一些對(duì)象。對(duì)象是理解面向?qū)ο蠹夹g(shù)的關(guān)鍵。你在學(xué)習(xí)之前可以看看現(xiàn)實(shí)生活中的對(duì)象,比如狗、桌子、電視、自行車等等。你可以發(fā)現(xiàn)現(xiàn)實(shí)世界中的對(duì)象有兩個(gè)共同特征:它們都有狀態(tài)和行為。比如狗有自己的狀態(tài)(比如名字、顏色、生育以及饑餓等等)和行為(比如搖尾巴等等)。同樣自行車也有自己的狀態(tài)(比如當(dāng)前檔位、兩個(gè)輪子等等)和行為(比如剎車、加速、減速以及改變檔位等等)。 而軟件對(duì)象實(shí)際上是現(xiàn)實(shí)世界對(duì)象的造型,因?yàn)樗瑯佑袪顟B(tài)和行為。一個(gè)軟件對(duì)象利用一個(gè)或者多個(gè)變量來(lái)維持它的狀態(tài)。變量是由用戶標(biāo)識(shí)符來(lái)命名的數(shù)據(jù)項(xiàng)。軟件對(duì)象用它的方法來(lái)執(zhí)行它的行為。方法是跟對(duì)象有關(guān)聯(lián)的函數(shù)(子程序)。 你可以利用軟件對(duì)象來(lái)代表現(xiàn)實(shí)世界中的對(duì)象。你可能想用一個(gè)動(dòng)畫(huà)程序來(lái)代表現(xiàn)實(shí)世界中的狗,或者用可以控制電子自行車的程序來(lái)代表現(xiàn)實(shí)世界的自行車。同樣你可以使用軟件對(duì)象來(lái)造型抽象的概念,比如,事件是一個(gè)用在GUI窗口系統(tǒng)的公共對(duì)象,它可以代表用戶按下鼠標(biāo)按鈕或者鍵盤(pán)上的按鍵的反應(yīng)。 如圖1是一個(gè)軟件對(duì)象的公共可視代表。 ![]() (圖1) 軟件對(duì)象的狀態(tài)和行為都可以用在對(duì)象中的變量和方法來(lái)表達(dá)。構(gòu)造現(xiàn)實(shí)世界的自行車的軟件對(duì)象要有指示自行車的當(dāng)前狀態(tài)的變量:速度為20mph,它的當(dāng)前檔位為第三檔。這些變量就是我們熟知的實(shí)例變量,因?yàn)樗鼈儼擞糜谔厥庾孕熊噷?duì)象的狀態(tài),并且在面向?qū)ο蠹夹g(shù)中,特殊的對(duì)象稱為實(shí)例。 如圖2所示,是作為軟件對(duì)象的自行車造型。 ![]() (圖2) 除了變量,軟件自行車同樣有用于剎車、改變踏板步調(diào)以及改變檔位的方法。這些方法就是熟知的實(shí)例方法因?yàn)樗鼈儥z查或者改變特殊自行車實(shí)例的狀態(tài)。 以上的對(duì)象圖顯示了對(duì)象的變量組成了圓心部分。方法處在變量的四周并且在程序中從其它對(duì)象隱藏了對(duì)象的核心。用保護(hù)方法的方法來(lái)包裝對(duì)象的變量稱為封裝。這個(gè)對(duì)象圖就是對(duì)象理想的表示法,也是面向?qū)ο笙到y(tǒng)設(shè)計(jì)者努力的最后目標(biāo)。然而這并不是全部的內(nèi)容。通常,出于某種現(xiàn)實(shí)的理由,對(duì)象可能暴露它的一些變量或者隱藏一些方法。在JAVA編程語(yǔ)言中,一個(gè)對(duì)象可以為變量和方法指定四種訪問(wèn)等級(jí)中的一種。這個(gè)訪問(wèn)等級(jí)決定哪個(gè)對(duì)象和類可以訪問(wèn)變量或者方法。在JAVA中訪問(wèn)變量和方法可以轉(zhuǎn)換為控制訪問(wèn)類的成員函數(shù)。封裝相關(guān)的變量和方法到一個(gè)簡(jiǎn)潔的軟件集是一個(gè)簡(jiǎn)單而且強(qiáng)有力的方法,它為軟件開(kāi)發(fā)者提供了兩個(gè)主要好處: 模塊性:對(duì)象的源代碼可以獨(dú)立于其它的對(duì)象源代碼來(lái)進(jìn)行編寫(xiě)和維護(hù)。同樣,對(duì)象可以很容易地在系統(tǒng)中傳遞。你可以將你的自行車對(duì)象給其它的對(duì)象,它仍然可以正常工作。 信息隱藏:一個(gè)對(duì)象如果有一個(gè)公共的界面,那么其它的對(duì)象就可以與之進(jìn)行通訊。這個(gè)對(duì)象可以維護(hù)私人的信息和方法,它可以在任何時(shí)候被改變而不影響依耐于它的其它對(duì)象。所以你不必理解自行車中齒輪的機(jī)理就可以使用它。 2.2什么是消息 軟件對(duì)象之間進(jìn)行交互作用和通訊是利用消息的。 單一的一個(gè)對(duì)象通常不是很有用的。相反,一個(gè)對(duì)象通常是一個(gè)包含了許多其它對(duì)象的更大的程序或者應(yīng)用程序。通過(guò)這些對(duì)象的交互作用,程序員可以獲得高階的功能以及更為復(fù)雜的行為。你的自行車如果不使用它的時(shí)候,它就是一堆鋁合金和橡膠,它沒(méi)有任何的活動(dòng)。而只有當(dāng)有其它的對(duì)象來(lái)和它交互的時(shí)候才是有用的。 軟件對(duì)象與其它對(duì)象進(jìn)行交互與通訊是利用發(fā)送給其它對(duì)象來(lái)實(shí)現(xiàn)的。當(dāng)對(duì)象A想對(duì)象B來(lái)執(zhí)行一個(gè)B中的方法,對(duì)象A就會(huì)消息給對(duì)象B。如圖3所示。 ![]() (圖3) 有時(shí)候,接收的對(duì)象需要更多的信息就至于它可以正確知道該如何做。比如,當(dāng)你想改變自行車的齒輪,你就必須指出哪個(gè)齒輪。這個(gè)信息是將信息作為參數(shù)來(lái)傳遞的。如圖4所示的現(xiàn)實(shí)了一個(gè)信息由三個(gè)組件組成: - 被尋址消息的對(duì)象(YourBicycle)
- 要執(zhí)行方法的名字(changeGears)
- 這個(gè)方法需要的所有參數(shù)(lowerGear)
![]() (圖4) 上面的三個(gè)組件對(duì)于接收方的對(duì)象執(zhí)行相應(yīng)的方法是給出了充分的信息。再也不需要其它的信息或者上下文了。 消息提供了兩個(gè)重要的好處: - 對(duì)象的行為是通過(guò)它的方法來(lái)表達(dá)的,因此消息傳遞支持所有在對(duì)象之間的可能的交互。
- 對(duì)象不需要在相同的進(jìn)程或者相同的機(jī)器上來(lái)發(fā)送和接收消息給其它的對(duì)象
2.3什么是類 類實(shí)際上是對(duì)某種類型的對(duì)象定義變量和方法的原型。 在現(xiàn)實(shí)世界中,你經(jīng)常看到相同類型的許多對(duì)象。比如 ,你的自行車只是現(xiàn)實(shí)世界中許多自行車的其中一輛。使用面向?qū)ο蠹夹g(shù),我們可以說(shuō)你的自行車是自行車對(duì)象類的一個(gè)實(shí)例。通常,自行車有一些狀態(tài)(當(dāng)前檔位、兩個(gè)輪子等等)以及行為(改變檔位、剎車等等)。但是,每輛自行車的狀態(tài)都是獨(dú)立的并且跟其它自行車不同。 當(dāng)廠家制造自行車的時(shí)候,廠商利用了自行車共有的特性來(lái)根據(jù)相同的藍(lán)圖制造許多自行車。如果制造一輛自行車就要產(chǎn)生一個(gè)新藍(lán)圖,那效率就太低了。 在面向?qū)ο筌浖校瑯拥兀梢宰屜嗤N類地許多對(duì)象來(lái)共有一些特性,比如矩形、雇員記錄、視頻夾等等。就象自行車制造商人,你可以利用相同種類的對(duì)象是相似的事實(shí)并且你可以為這些對(duì)象創(chuàng)建一個(gè)藍(lán)圖。對(duì)對(duì)象的軟件藍(lán)圖叫做類。 自行車的類需要定義一些實(shí)例變量來(lái)包括當(dāng)前檔位、當(dāng)前速度等等。這個(gè)類將為實(shí)例方法定義和提供實(shí)施方法,它允許騎車者改變檔位、剎車以及改變腳踏板的節(jié)奏,如圖5所示: ![]() (圖5) 當(dāng)你創(chuàng)建了自行車類以后,你可以從這個(gè)類創(chuàng)建任意個(gè)自行車對(duì)象。當(dāng)你創(chuàng)建了一個(gè)類的實(shí)例后,系統(tǒng)將為這個(gè)對(duì)象和的實(shí)例變量分配內(nèi)存。每個(gè)實(shí)例將給所有實(shí)例變量的副本定義在類中。如圖6所示: ![]() (圖6) 除了實(shí)例變量,類還要定義類的變量。類變量包含了被類所有實(shí)例共享的信息。比如,假設(shè)所有的自行車有相同的檔位數(shù)。在本例子中,要定義一個(gè)實(shí)例變量來(lái)容納檔位數(shù)。每一個(gè)實(shí)例都會(huì)有變量的副本,但是在每一個(gè)實(shí)例中數(shù)值都是相同的。在這樣的情況下,你可以定義一個(gè)類變量來(lái)包含檔位數(shù),這樣所有的類的實(shí)例都共享這個(gè)變量。如果一個(gè)對(duì)象改變了變量,它就為改變那個(gè)類的所有對(duì)象。類同樣可以定義類方法。你可以直接從類中調(diào)用類方法,然而你必須在特定的實(shí)例中調(diào)用實(shí)例方法。如圖7所示。 ![]() (圖7)
2.4實(shí)例和類成員 2.4.1理解實(shí)例和類成員 下面詳細(xì)討論一下實(shí)例和類成員,具體涉及變量和方法以及類變量和方法: 你這樣聲明一個(gè)成員變量,比如在類Myclass中有一個(gè)float型的aFloat: class MyClass { float aFloat; } 這樣你就聲明一個(gè)實(shí)例變量。每次你創(chuàng)建一個(gè)類的實(shí)例的時(shí)候,系統(tǒng)就為實(shí)例創(chuàng)建了類的每一個(gè)實(shí)例變量的副本。你可以從對(duì)象中訪問(wèn)對(duì)象的實(shí)例變量。 實(shí)例變量跟類變量是不一樣的,類變量示使用靜態(tài)修改量來(lái)聲明的。不管類創(chuàng)建了多少個(gè)實(shí)例,系統(tǒng)為每個(gè)類變量分配了類變量。系統(tǒng)為類變量分配的內(nèi)存是在它第一次調(diào)用類的時(shí)候發(fā)生的。所有的實(shí)例共享了類的類變量的相同副本。你可以通過(guò)實(shí)例或者通過(guò)類本身來(lái)訪問(wèn)類變量。 它們的方法是類似的:你的類可以有實(shí)例方法和類方法。實(shí)例方法是對(duì)當(dāng)前對(duì)象的實(shí)例變量進(jìn)行操作的,而且訪問(wèn)類變量。另外一個(gè)方法,類方法不能訪問(wèn)定義在類中的實(shí)例變量,除非它們創(chuàng)建一個(gè)新的對(duì)象并通過(guò)對(duì)象來(lái)訪問(wèn)它們。同樣,類方法可以在類中被調(diào)用,你不必需要一個(gè)實(shí)例來(lái)調(diào)用一個(gè)類方法。 缺省地,除非其它的成員被指定,一個(gè)定義在類中成員就是一個(gè)實(shí)例成員。這個(gè)在下面定義的類有一個(gè)實(shí)例變量,有一個(gè)整型的x,兩個(gè)實(shí)例方法x和setX,它們?cè)O(shè)置其它對(duì)象以及查詢x的數(shù)值。 class AnIntegerNamedX { int x; public int x() { return x; } public void setX(int newX) { x = newX; } } 每次你從一個(gè)類實(shí)例化一個(gè)新的對(duì)象,你可以得到每個(gè)類的實(shí)例變量的副本。這些副本都是跟新對(duì)象有關(guān)系的。因此,每次你從這個(gè)類實(shí)例化一個(gè)新的AnIntegerNamedX對(duì)象的時(shí)候,你得以得到跟新的AnIntegerNamedX對(duì)象有關(guān)的新副本。 一個(gè)類的所有實(shí)例共享一個(gè)實(shí)例方法的相同的實(shí)行;所有的AnIntegerNamedX實(shí)例都共享x和setX的相同執(zhí)行。這里注意,兩個(gè)方法x和setX是指對(duì)象的實(shí)例變量x。但是,你可能會(huì)問(wèn):如果所有AnIntergerNamedX共享x和setX的相同執(zhí)行,會(huì)不會(huì)造成模棱兩可的狀態(tài)?答案當(dāng)然是:不是。在實(shí)例方法中,實(shí)例變量的名字是指當(dāng)前對(duì)象的實(shí)例變量,假如實(shí)例變量不是由一個(gè)方法參數(shù)來(lái)隱藏的。這樣在x和setX中,x就等價(jià)于這個(gè)x,而不會(huì)造成混亂。 對(duì)于AnIntegerNamedX外部的對(duì)象如果想訪問(wèn)x,它必須通過(guò)特定的AnIntegerNamedX的實(shí)例來(lái)實(shí)現(xiàn)。假如這個(gè)代碼片段處在其它對(duì)象的方法中。它創(chuàng)建了兩種不同類型的AnIntegerNamedX,它設(shè)置了x為不同的數(shù)值,然后顯示它們: AnIntegerNamedX myX = new AnIntegerNamedX(); AnIntegerNamedX anotherX = new AnIntegerNamedX(); myX.setX(1); anotherX.x = 2; System.out.println("myX.x = " + myX.x()); System.out.println("anotherX.x = " + anotherX.x()); 這里注意,代碼使用setX來(lái)為myX設(shè)置x的值,而直接給anotherX.x指定一個(gè)數(shù)值。不管用什么方法,代碼是在操作兩個(gè)不同的x副本:一個(gè)包含在myX對(duì)象中一,另外一個(gè)包含在anotherX對(duì)象中。其輸出是用以下代碼片段來(lái)實(shí)現(xiàn)的: myX.x = 1 anotherX.x = 2 上面代碼顯示了類AnIntegerNamedX的每一個(gè)實(shí)例有自己實(shí)例變量x的副本以及每個(gè)x有自己的數(shù)值。 你可以在聲明成員變量的時(shí)候,指定變量是一個(gè)類變量而不是一個(gè)實(shí)例變量。相似地,你可以指定方法是一個(gè)類方法而不是一個(gè)實(shí)例方法。系統(tǒng)在第一次調(diào)用類來(lái)定義變量的時(shí)候創(chuàng)建了一個(gè)類變量的副本。所有的類實(shí)例共享了類變量的相同副本。類方法可以只操作類變量,它們不能訪問(wèn)定義在類中的實(shí)例變量。 為了指定一個(gè)成員變量為一個(gè)類變量,你可以使用static關(guān)鍵字。比如,我們可以修改一下上面的AnIntegerNamedX類,使得x變量現(xiàn)在是一個(gè)類變量: class AnIntegerNamedX { static int x; public int x() { return x; } public void setX(int newX) { x = newX; } } 現(xiàn)在設(shè)置它們的x數(shù)值并顯示不同的輸出: myX.x = 2 anotherX.x = 2 這次的輸出不同,是因?yàn)閤現(xiàn)在是一個(gè)類變量,所以就只有這個(gè)變量的副本,它是被AnIntegerNamedX的所有實(shí)例所共享的,包括myX和anotherX。當(dāng)你在其它實(shí)例中調(diào)用setX的時(shí)候,你可以為所有的AnIntegerNamedX的所有實(shí)例改變x的數(shù)值。 同樣,當(dāng)我們聲明一個(gè)方法的時(shí)候,你可以指定方法為類方法而不是實(shí)例方法。類方法只可以在類變量中進(jìn)行操作,并且不能訪問(wèn)定義在類中的所有實(shí)例變量。 為了指定方法為類方法,你可以在方法聲明處使用static關(guān)鍵字。下面,我們?cè)俅蝸?lái)修改AnIntegerNamedX類,使它的成員變量x為一個(gè)實(shí)例變量,以及它的兩個(gè)方法為類方法: class AnIntegerNamedX { int x; static public int x() { return x; } static public void setX(int newX) { x = newX; } } 當(dāng)你想編譯這個(gè)版本的AnIntegerNamedX,編譯器就會(huì)顯示如下的錯(cuò)誤: AnIntegerNamedX.java:4: Can't make a static reference to nonstatic variable x in class AnIntegerNamedX. return x; ^ 出現(xiàn)這些錯(cuò)誤的原因是類方法不能訪問(wèn)實(shí)例變量,除非方法先創(chuàng)建AnIntegerNamedX的一個(gè)實(shí)例并且通過(guò)它來(lái)訪問(wèn)變量。 下面我們修改一下AnIntegerNamedX,讓x變量成為類變量: class AnIntegerNamedX { static int x; static public int x() { return x; } static public void setX(int newX) { x = newX; } } 現(xiàn)在為x設(shè)置數(shù)值,并打印出x數(shù)值: myX.x = 2 anotherX.x = 2 再一次,我們通過(guò)myX來(lái)改變x,并將它改變?yōu)锳nIntegerNamedX的其它實(shí)例。 實(shí)例成員和類成員之間的另外一個(gè)差別是類成員可以從類本身進(jìn)行訪問(wèn)。你不必實(shí)例化類來(lái)訪問(wèn)它的類成員。下面讓我們編寫(xiě)一段代碼來(lái)直接從AnIntegerNamedX類中訪問(wèn)x和setX: . . . AnIntegerNamedX.setX(1); System.out.println("AnIntegerNamedX.x = " + AnIntegerNamedX.x()); . . . 值得一提的是,你現(xiàn)在已經(jīng)不用再創(chuàng)建myX和anotherX了。你可以設(shè)置x并直接AnIntegerNamedX類中檢索x。你不能利用實(shí)例成員來(lái)處理它,你只能從一個(gè)對(duì)象來(lái)調(diào)用實(shí)例方法并且只可以從對(duì)象中訪問(wèn)實(shí)例變量。而你可以從類的實(shí)例或者從類本身來(lái)訪問(wèn)類變量和方法。 2.4.2初始化實(shí)例和類成員 下面講講初始化實(shí)例和類成員: 你可以在類中定義它們的時(shí)候,使用static初始化程序和實(shí)例初始化程序來(lái)為類和實(shí)例成員提供初始化數(shù)值: class BedAndBreakfast { static final int MAX_CAPACITY = 10; boolean full = false; } 這個(gè)對(duì)于原始數(shù)據(jù)類型是沒(méi)有問(wèn)題的。有時(shí)候,它可以用在創(chuàng)建數(shù)組和對(duì)象。但是這個(gè)初始化表單有它的限制,如下: - 初始化程序只可以執(zhí)行用賦值語(yǔ)句表達(dá)的初始化 。
- 初始化程序不能調(diào)用任何的包含異常錯(cuò)誤的方法。
- 如果初始化程序調(diào)用一個(gè)包含異常錯(cuò)誤的方法,它不能進(jìn)行錯(cuò)誤恢復(fù)。
如果你有一些初始化要完成,可能有些不能在初始化程序?qū)崿F(xiàn),因?yàn)槌霈F(xiàn)了上面的限制之一,這時(shí)你不得不將初始化代碼隨意放置了。為了初始化類成員,在static初始化塊中放置初始化代碼。為了初始化實(shí)例成員,就要在構(gòu)造函數(shù)中放置初始化代碼了。 2.4.3 Static初始化塊 下面再講講Static初始化塊 下面舉一個(gè)例子,如圖8所示: ![]() (圖8) errorStrings源代碼必須在static初始化塊中被初始化。這是因?yàn)殄e(cuò)誤恢復(fù)必須在源代碼沒(méi)有被找到得時(shí)候才被執(zhí)行。同時(shí),errorStrings是一個(gè)類成員,它不能在構(gòu)造函數(shù)中被初始化。在前面得例子中一,一個(gè)static初始化塊是以static關(guān)鍵字開(kāi)頭得,并且JAVA代碼是用大括號(hào)“{}”括起來(lái)的。 一 個(gè)類可以有許多static初始化塊,它可以出現(xiàn)在類中任何地方。系統(tǒng)保證static輸出化塊以及static初始化程序是按它們?cè)谠创a中的順序被調(diào)用的。
2.4.4 初始化實(shí)例成員 如果你想初始化一個(gè)實(shí)例變量而且不能在變量聲明處來(lái)處理它,那么就只能在構(gòu)造函數(shù)中來(lái)為這個(gè)類初始化了。假如errorStrings是一個(gè)實(shí)例變量而不是一個(gè)類變量,你就可以使用以下的代碼來(lái)初始化它: import java.util.ResourceBundle; class Errors { ResourceBundle errorStrings; Errors() { try { errorStrings = ResourceBundle. getBundle("ErrorStrings"); } catch (java.util.MissingResourceException e) { // error recovery code here } } } 現(xiàn)在代碼是在構(gòu)造函數(shù)中為類來(lái)初始化這個(gè)errorStrings的。 有時(shí),類包含了許多構(gòu)造函數(shù)并且每個(gè)構(gòu)造函數(shù)允許調(diào)用者為新對(duì)象的不同實(shí)例變量提供初始化數(shù)值。比如,java.awt.Rectangle有以下的三個(gè)構(gòu)造函數(shù): Rectangle(); Rectangle(int width, int height); Rectangle(int x, int y, int width, int height); Rectangle()構(gòu)造函數(shù)沒(méi)有任何的參數(shù),所以它不能讓用戶大小或者原點(diǎn)和大小提供初始化數(shù)值;而其它的兩個(gè)構(gòu)造函數(shù),它可以讓用戶設(shè)置初始數(shù)值。 然而,所有的實(shí)例變量(原點(diǎn)和大小)都必須初始化。在這個(gè)例子中,類經(jīng)常有一個(gè)構(gòu)造函數(shù)來(lái)完成所有的初始化。其它的構(gòu)造函數(shù)調(diào)用這個(gè)構(gòu)造函數(shù)并且提供給它參數(shù)或者缺省數(shù)值。比如下面是以上所說(shuō)的三個(gè)構(gòu)造函數(shù),它們初始化如下: Rectangle() { this(0,0,0,0); } Rectangle(int width, int height) { this(0,0,width,height); } Rectangle(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } JAVA語(yǔ)言支持實(shí)例初始化塊,你可以放心使用它。這里建議使用構(gòu)造函數(shù)來(lái)初始化,主要有以下三個(gè)原因: - 所有的初始化代碼處在一個(gè)地方,這樣使得代碼更容易維護(hù)和閱讀。
- 缺省值可以清除地知道。
- 構(gòu)造函數(shù)廣泛被JAVA程序設(shè)計(jì)人員所熟悉,包括相對(duì)新的JAVA程序設(shè)計(jì)人員,而實(shí)例初始化程序不能,而且他可能導(dǎo)致其它JAVA程序設(shè)計(jì)員的困惑。
2.4.5 對(duì)象和類 你可能會(huì)注意到對(duì)象和類看起來(lái)很相似。在現(xiàn)實(shí)世界中,類和對(duì)象之間的區(qū)別經(jīng)常是讓程序員困惑的源泉。在現(xiàn)實(shí)世界中,很明顯,類不能是它們描述的對(duì)象本身。然而,在軟件中很困難來(lái)區(qū)分類和對(duì)象。有部分原因是軟件對(duì)象只是現(xiàn)實(shí)世界中的電子模型或者是抽象概念。但是也因?yàn)閷?duì)象通常有時(shí)是指類和實(shí)例。 2.5什么是繼承 一個(gè)類可以從它的父類繼承狀態(tài)和行為。繼承為組織和構(gòu)造軟件程序提供了一個(gè)強(qiáng)大的和自然的機(jī)理。 總得說(shuō)來(lái),對(duì)象是以類得形式來(lái)定義得。你可能現(xiàn)在已經(jīng)可以從它類知道許多對(duì)象了。即使你如知道,如果我告訴你它是一輛自行車,你就會(huì)知道它有兩個(gè)輪子和腳踏板等等。面向?qū)ο笙到y(tǒng)就更深入一些了,它允許類在其它類中定義。比如,山地自行車、賽車以及串座雙人自行車都是各種各樣的自行車。在面向?qū)ο蠹夹g(shù)中,山地自行車、賽車以及串座雙人自行車都是自行車類的子類。同樣地,自行車類是山地自行車、賽車以及串座雙人自行車的父類。這個(gè)父子關(guān)系可以如圖9所示: ![]() (圖9) 每一個(gè)子例從父類中繼承了狀態(tài)。山地自行車、賽車以及串座雙人自行車共享了這些狀態(tài):速度等。同樣,每一個(gè)子類繼承類從父類的方法,山地自行車、賽車以及串座雙人自行車共享了這些行為:剎車、改變腳踏速度等等。 然而,子類不能受到父類提供的狀態(tài)和行為的限制。子類可以增加變量和方法到從父類繼承而來(lái)的變量和方法。比如,串座雙人自行車有兩個(gè)座位,這是它的父類沒(méi)有的。 子類同樣可以重載繼承的方法并且為這些方法提供特殊執(zhí)行方法。比如 ,如果你有一個(gè)山地自行車有額外 的齒輪設(shè)置,你就可以重載改變齒輪方法來(lái)使騎車者可以使用這些新的齒輪。 你也不能受限于繼承的一個(gè)層次。繼承樹(shù)或者類的分級(jí)結(jié)構(gòu)可以是很深。方法和變量是逐級(jí)繼承的。總的來(lái)說(shuō),在分級(jí)結(jié)構(gòu)的越下方,就有越多的行為。 如果對(duì)象類處于分級(jí)結(jié)構(gòu)的頂端,那么每個(gè)類都是它的后代(直接地或者是間接地)。一種類型的對(duì)象保留任何對(duì)象的一個(gè)引用,比如類或者數(shù)組的一個(gè)實(shí)例。對(duì)象提供了行為,這些行為是運(yùn)行在JAVA虛擬機(jī)所需要的。比如,所有類繼承了對(duì)象的toString方法,它返回了代表對(duì)象的字符串。 下面說(shuō)說(shuō)我們?yōu)槭裁匆褂美^承,它到底有哪些好處呢?好處是有的: - 子類提供了特殊的行為,這是在父類中所沒(méi)有的。通過(guò)使用繼承,程序員可以多次重新使用在父類中的代碼。
- 程序員可以執(zhí)行父類(稱為抽象類)來(lái)定義總的行為。這個(gè)抽象的父類可以定義并且部分執(zhí)行行為,但是絕大多數(shù)的父類是未定義和未執(zhí)行的。其它的部分由程序員來(lái)實(shí)現(xiàn)特殊的子類。
2.6什么是接口 接口是一個(gè)收集方法和常數(shù)表單的契約。當(dāng)類執(zhí)行一個(gè)接口,它就許諾聲明在那個(gè)接口中執(zhí)行所有的方法。 接口是一個(gè)設(shè)備或者一個(gè)系統(tǒng),它是用于交互的無(wú)關(guān)的實(shí)體。根據(jù)這個(gè)定義,遠(yuǎn)程控制是一個(gè)在你和電視的接口;而英語(yǔ)是兩個(gè)人之間的接口;強(qiáng)制在軍事中的行為協(xié)議是不同等價(jià)人之間的接口。在JAVA語(yǔ)言中,接口是一個(gè)設(shè)備,它是用來(lái)與其它對(duì)象交互的設(shè)備。一個(gè)接口可能對(duì)一個(gè)協(xié)議是類似的。實(shí)際上,其它面向?qū)ο笳Z(yǔ)言有接口的功能,但它們調(diào)用它們的接口協(xié)議。 自行車類和它的類分級(jí)結(jié)構(gòu)定義了什么是自行車。但是自行車在其它方面與現(xiàn)實(shí)世界交互作用,例如,倉(cāng)庫(kù)中的自行車可以由一個(gè)存貨程序來(lái)管理。一個(gè)存貨程序不關(guān)心管理項(xiàng)目的哪一類只要項(xiàng)目提供某一信息,比如價(jià)格和跟蹤數(shù)字。取代強(qiáng)迫類與其它無(wú)關(guān)項(xiàng)的關(guān)系,存貨程序建立了通訊的協(xié)議。這個(gè)協(xié)議是由包含在接口中的常數(shù)和方法定義組成的。這個(gè)存貨清單接口將要定義(但不執(zhí)行)方法來(lái)設(shè)置和得到零售價(jià)格,指定跟蹤數(shù)字等等。 為了在存貨清單程序中操作,自行車類必須在執(zhí)行接口的時(shí)候遵守這個(gè)協(xié)議。當(dāng)一個(gè)了執(zhí)行一個(gè)接口的時(shí)候,類遵守定義在接口中的所有方法。因此,自行車將為這些設(shè)置和獲得零售價(jià)格并指定跟蹤數(shù)值等等的方法提供執(zhí)行。 你可以使用接口來(lái)定義一個(gè)行為的協(xié)議,這個(gè)行為可以有在類分級(jí)結(jié)構(gòu)中任何類來(lái)執(zhí)行。接口的主要好處有一下幾點(diǎn): - 不用人工強(qiáng)迫類關(guān)系在無(wú)關(guān)類中截獲相似處。
- 聲明想執(zhí)行的一個(gè)或者更多類的方法。
- 在不暴露對(duì)象的類的前提下,暴露對(duì)象的編程接口。
2.7怎樣將這些面向?qū)ο蟮母拍钷D(zhuǎn)換為代碼 2.7.1ClickMe的源代碼和Applet標(biāo)簽 這一小節(jié)將給你展現(xiàn)創(chuàng)建對(duì)象、執(zhí)行類、發(fā)送消息、創(chuàng)建一個(gè)父類以及執(zhí)行一個(gè)接口的代碼。 以下是一個(gè)applet(applet是用JAVA編程語(yǔ)言編寫(xiě)的程序,它可以運(yùn)行在兼容JAVA平臺(tái)的網(wǎng)絡(luò)瀏覽器,比如HotJava或者Netscape Navigator)的程序,名為ClickMe。如圖10所示,當(dāng)你點(diǎn)擊方框內(nèi)任何地方,一個(gè)紅點(diǎn)就會(huì)出現(xiàn)。 ![]() (圖10) 提示:上面的applet需要JDK1.1。如果你使用老的不支持JDK1.1的瀏覽器,你將不能運(yùn)行這個(gè)applet。相反,你需要在一個(gè)1.1瀏覽器上來(lái)看這個(gè)網(wǎng)頁(yè),比如在HotJava、JDK Applect瀏覽器(appletviewer)或者某個(gè)版本的Netscape Navigator和Internet Explorer。 下面具體解釋一下這個(gè)Applet。 ClickMe Applet是一個(gè)相對(duì)簡(jiǎn)單的程序因此它的代碼就短了多了。但是,如果你還沒(méi)有太多的編程經(jīng)驗(yàn),你可以發(fā)現(xiàn)這些代碼也不是那么容易的。我們不要求你馬上理解程序中的每個(gè)問(wèn)題,并且這節(jié)教程也不是講了十分詳細(xì)的。這里的目的示暴露一些源代碼給你并且跟你剛才所學(xué)道的概念和技術(shù)聯(lián)系上。你將在以后的教程中學(xué)到更詳細(xì)的內(nèi)容。 2.7.1ClickMe的源代碼和Applet標(biāo)簽 為了編譯這個(gè)applet你需要兩個(gè)源文件:ClickMe.java和Spot.java。為了運(yùn)行這個(gè)applet你需要利用這個(gè)applet標(biāo)簽來(lái)創(chuàng)建一個(gè)html文件: 然后裝載網(wǎng)頁(yè)到瀏覽器或者appletviewer工具。并且確保所有必要的文件都在相同的目錄中。 如圖11所示: ![]() (圖11)
2.7.2 ClickMe Applet中的對(duì)象 在這個(gè)applet中有許多對(duì)象。兩個(gè)最明顯的是:applet本身和紅點(diǎn)。 瀏覽器在包含applet的HTML代碼中碰到applet標(biāo)簽的時(shí)候就創(chuàng)建了applet對(duì)象。這個(gè)applet標(biāo)簽從創(chuàng)建applet對(duì)象的地方提供類的名字。在本例子中,這個(gè)類的名字為ClickMe。 ClickME.applet將創(chuàng)建一個(gè)對(duì)象來(lái)在屏幕上畫(huà)出點(diǎn)。每次你在applet中點(diǎn)擊鼠標(biāo)的時(shí)候,applet就將通過(guò)改變對(duì)象的x和y坐標(biāo)來(lái)移動(dòng)紅點(diǎn)。這個(gè)點(diǎn)不是自己畫(huà)出來(lái)的,它是applet畫(huà)出的,它是根據(jù)包含在點(diǎn)對(duì)象中的信息畫(huà)出的。 除了前面兩個(gè)明顯的對(duì)象,另外還有一些看不見(jiàn)的對(duì)象呢。有代表三種顏色(黑、白、紅)的三個(gè)對(duì)象以及代表點(diǎn)擊鼠標(biāo)的用戶動(dòng)作的事件對(duì)象等等。
2.7.3ClickMe Applet中的類 因?yàn)榇碓谄聊簧宵c(diǎn)的對(duì)象是很簡(jiǎn)單,接下來(lái)讓我們看看這個(gè)名為spot的類吧。它聲明了三個(gè)實(shí)例變量:包括點(diǎn)半徑的size,包含點(diǎn)當(dāng)前水平位置的x坐標(biāo)以及包含點(diǎn)當(dāng)前垂直位置的y坐標(biāo): public class Spot { //實(shí)例變量 public int size; public int x, y; //構(gòu)造函數(shù) public Spot(int intSize) { size = intSize; x = -1; y = -1; } } 另外,類有一個(gè)構(gòu)造函數(shù),它用來(lái)初始化由類創(chuàng)建的新對(duì)象。構(gòu)造函數(shù)跟類有相同的名字。這個(gè)構(gòu)造函數(shù)初始化所有三個(gè)對(duì)象的變量。Size的初始化數(shù)值是在調(diào)用的時(shí)候座位參數(shù)提供的。x和y變量都被設(shè)置為-1,這里-1的目的是為了讓點(diǎn)在開(kāi)始的時(shí)候處于屏幕的外面,即產(chǎn)生假不可視的效果。 這個(gè)applet是在applet初始化的時(shí)候創(chuàng)建了一個(gè)新的點(diǎn)對(duì)象。下面是applet類的相關(guān)代碼: private Spot spot = null; private static final int RADIUS = 7; ... spot = new Spot(RADIUS); 第一行聲明了一個(gè)名為spot的變量,它是Spot數(shù)據(jù)類型,并且初始化這個(gè)變量為NULL。第二行聲明了一個(gè)整型變量,名為RADIUS,它的值為7。最后一行是創(chuàng)建一個(gè)對(duì)象。New關(guān)鍵字為對(duì)象分配了內(nèi)存空間。Spot(RADIUS)調(diào)用了上面已經(jīng)描述了的構(gòu)造函數(shù)并且傳遞RADIUS數(shù)值,這樣點(diǎn)對(duì)象的size就被設(shè)置為7。如圖12所示的左圖代表了Spot類,而右邊的是代表了spot對(duì)象。 ![]() (圖12) 2.7.4ClickMe Applet中的消息 就如所知道的,對(duì)象A可以使用消息來(lái)請(qǐng)求對(duì)象B做一些事情,一個(gè)消息有三個(gè)組成部分: - 消息被尋址的對(duì)象
- 要執(zhí)行執(zhí)行方法的名字
- 方法需要的任何參數(shù)
在ClickMe applet種有以下兩行這樣的代碼: g.setColor(Color.white); g.fillRect(0, 0, getSize().width - 1, getSize().height - 1); 這兩個(gè)消息都是從applet到名為g的對(duì)象。其中g(shù)是一個(gè)圖形對(duì)象,它知道怎樣在屏幕上簡(jiǎn)單畫(huà)一些形狀或者文本。這個(gè)對(duì)象在瀏覽器指示applet來(lái)畫(huà)的時(shí)候提供了applet。上面代碼的第一行設(shè)置顏色為白色,第二行是填充一個(gè)指定大小的矩形區(qū)域,它的顏色為白色。如圖13所示,是一個(gè)消息的三個(gè)組成部分: ![]() (圖13)
2.7.5 ClickMe Applet中的繼承 為了在瀏覽器種運(yùn)行,對(duì)象必須是一個(gè)applet。這就意味著對(duì)象必須是類的一個(gè)實(shí)例,這個(gè)類是從由JAVA平臺(tái)提供的Applet類派生而來(lái)的。 ClickMe applet對(duì)象是一個(gè)ClickMe類的一個(gè)實(shí)例,它是這樣聲明的: public class ClickMe extends Applet implements MouseListener { ... } 上面的語(yǔ)句就產(chǎn)生了Applet的一個(gè)子類。ClickMe繼承了父類的許多功能,包括初始化、由瀏覽器來(lái)開(kāi)始和結(jié)束、在瀏覽器區(qū)域畫(huà)圖以及對(duì)接收到的鼠標(biāo)事件注冊(cè)。除了有了這些功能,ClickMe類還要實(shí)現(xiàn)以下的功能:它的畫(huà)圖代碼在paint的方法中,初始化代碼必須在init方法中等等。 public void init() { ... // 這里加入ClickMe的初始化代碼 } public void paint(Graphics g) { ... // 這里加入ClickMe的畫(huà)圖代碼 } 2.7.6 ClickMe Applet中的接口 ClickMe applet是通過(guò)在鼠標(biāo)點(diǎn)擊出顯示一個(gè)紅點(diǎn)來(lái)響應(yīng)鼠標(biāo)的點(diǎn)擊事件。如果對(duì)象想通知鼠標(biāo)點(diǎn)擊,JAVA平臺(tái)事件系統(tǒng)需要對(duì)象執(zhí)行MouseListener接口。這個(gè)對(duì)象必須同時(shí)作為鼠標(biāo)監(jiān)聽(tīng)器來(lái)注冊(cè)。 這個(gè)MouseListener接口聲明了五種不同的志芋工,每種方法是用在鼠標(biāo)被點(diǎn)擊的時(shí)候?qū)Σ煌髽?biāo)事件的調(diào)用。當(dāng)鼠標(biāo)被點(diǎn)擊的時(shí)候,或者當(dāng)鼠標(biāo)移動(dòng)到applet外面的時(shí)候等等。 下面是ClickMe applet完整的代碼。其中mousePressed是處理鼠標(biāo)事件的: 2.8 面向?qū)ο蟾拍畹膯?wèn)題和練習(xí) 本節(jié)教程測(cè)試一下你對(duì)對(duì)象、類、消息等等的理解,我們是通過(guò)做一些練習(xí)以及回答一些問(wèn)題來(lái)進(jìn)行的。 2.8.1 問(wèn)題 你可以使用API文檔來(lái)回答這些問(wèn)題: - ClickMe applet使用Color.red來(lái)設(shè)置畫(huà)圖顏色為紅色。其它有什么顏色可以象這樣來(lái)使用?
- 怎樣設(shè)置顏色為紫色(purple)?
2.8.2 練習(xí) 現(xiàn)在,利用你從API文檔中學(xué)到的知識(shí)來(lái)修改ClickMe applet。為了編譯這個(gè)程序,你同樣需要Spot.java文件。具體修改如下: - 修改這個(gè)applet來(lái)畫(huà)一個(gè)綠色的方框而不是一個(gè)紅點(diǎn)。
- 修改這個(gè)applet來(lái)用紫色顯示你的名字而不是一個(gè)紅點(diǎn)。
|