書(shū)名: Scala編程(第5版)作者名: (德)馬丁·奧德斯基等本章字?jǐn)?shù): 1693字更新時(shí)間: 2022-05-06 15:51:35
4.1 類(lèi)、字段和方法
類(lèi)是對(duì)象的藍(lán)本(blueprint)。一旦定義好一個(gè)類(lèi),就可以用new關(guān)鍵字根據(jù)這個(gè)類(lèi)藍(lán)本創(chuàng)建對(duì)象。例如,有了下面這個(gè)類(lèi)定義:

就可以用如下代碼創(chuàng)建ChecksumAccumulator的對(duì)象:

在類(lèi)定義中,需要填入字段(field)和方法(method),這些被統(tǒng)稱為成員(member)。通過(guò)val或var定義的字段是指向?qū)ο蟮淖兞浚ㄟ^(guò)def定義的方法則包含了可執(zhí)行的代碼。字段保留了對(duì)象的狀態(tài),或者說(shuō)數(shù)據(jù),而方法用這些數(shù)據(jù)來(lái)對(duì)對(duì)象執(zhí)行計(jì)算。如果你實(shí)例化一個(gè)類(lèi),則運(yùn)行時(shí)會(huì)指派一些內(nèi)存來(lái)保存對(duì)象的狀態(tài)圖(即它的變量的內(nèi)容)。例如,如果你定義了一個(gè)ChecksumAccumulator類(lèi)并給它一個(gè)名稱為sum的var字段:

然后用如下代碼實(shí)例化兩次:

則內(nèi)存中這兩個(gè)對(duì)象看上去可能是這個(gè)樣子的:

由于sum這個(gè)定義在ChecksumAccumulator類(lèi)中的字段是var,而不是val,因此可以在后續(xù)代碼中對(duì)其重新賦予不同的Int值,如:

如此一來(lái),內(nèi)存中的對(duì)象看上去就如同:

關(guān)于這張圖需要注意的一點(diǎn)是總共有兩個(gè)sum變量,一個(gè)位于acc指向的對(duì)象里,而另一個(gè)位于csa指向的對(duì)象里。字段又叫作實(shí)例變量(instance variable),因?yàn)槊總€(gè)實(shí)例都有自己的變量。這些實(shí)例變量合在一起,構(gòu)成了對(duì)象在內(nèi)存中的映像。從圖中不難看出,不僅有兩個(gè)sum變量,而且當(dāng)你改變其中一個(gè)變量的值時(shí),另一個(gè)變量并不會(huì)受到影響。
本例中另一個(gè)值得注意的是,可以修改acc指向的對(duì)象,盡管acc本身是val。由于acc和csa都是val而不是var,因此不能將它們重新賦值并指向其他的對(duì)象。例如,如下代碼會(huì)報(bào)錯(cuò):

因此,能夠確定的是,acc永遠(yuǎn)指向那個(gè)你在初始化的時(shí)候用的ChecksumAccumulator對(duì)象,但隨著時(shí)間推移,這個(gè)對(duì)象中包含的字段是有可能改變的。
追求健壯性的一個(gè)重要手段是確保對(duì)象的狀態(tài)(它的實(shí)例變量的值)在其整個(gè)生命周期內(nèi)都是有效的。首先通過(guò)將字段標(biāo)記為私有(private)來(lái)防止外部直接訪問(wèn)字段。因?yàn)樗接凶侄沃荒鼙欢x在同一個(gè)類(lèi)中的方法訪問(wèn),所有對(duì)狀態(tài)的更新操作的代碼都在類(lèi)的內(nèi)部。要將某個(gè)字段聲明為私有,可以在字段前加上private這個(gè)訪問(wèn)修飾符,如:

有了這樣的ChecksumAccumulator類(lèi)的定義,任何試圖通過(guò)外部訪問(wèn)sum的操作都會(huì)失敗:

注意
在Scala中,使得成員允許公共訪問(wèn)(public)的方式是,不在成員前面顯式地給出任何訪問(wèn)修飾符。換句話說(shuō),對(duì)于那些在Java中可能會(huì)用“public”的地方,到了Scala中,什么都不說(shuō)就對(duì)了。公共訪問(wèn)是Scala的默認(rèn)訪問(wèn)級(jí)別。
由于sum是私有的,唯一能訪問(wèn)sum的代碼都定義在類(lèi)自身中。因此,ChecksumAccumulator對(duì)于其他人來(lái)說(shuō)沒(méi)什么用處,除非給它定義一些方法:

ChecksumAccumulator現(xiàn)在有兩個(gè)方法,即add和checksum,都是函數(shù)定義的基本形式,如圖2.1(26頁(yè))所展示的那樣。[1]
傳遞給方法的任何參數(shù)都能在方法內(nèi)部使用。Scala方法參數(shù)的一個(gè)重要特征是它們都是val而不是var。[2]因此,如果你試圖在Scala的方法中對(duì)入?yún)⒅匦沦x值,則編譯會(huì)報(bào)錯(cuò):

雖然在當(dāng)前版本的ChecksumAccumulator中,add方法和checksum方法正確地實(shí)現(xiàn)了預(yù)期的功能,但是還可以用更精簡(jiǎn)的風(fēng)格來(lái)表達(dá)。checksum方法最后的return是多余的,可以將其去掉。在沒(méi)有任何顯式的return語(yǔ)句時(shí),Scala方法返回的是該方法計(jì)算出的最后一個(gè)(表達(dá)式的)值。
事實(shí)上,我們推薦的方法風(fēng)格是避免使用任何顯式的return語(yǔ)句,尤其是多個(gè)return語(yǔ)句。與此相反,盡量將每個(gè)方法當(dāng)作一個(gè)最終交出某個(gè)值的表達(dá)式。這樣的哲學(xué)鼓勵(lì)你編寫(xiě)短小的方法,將大的方法拆成小的方法。另一方面,設(shè)計(jì)中的選擇是取決于上下文的,如果你確實(shí)想讓方法帶有多個(gè)顯式的return,Scala也允許你這樣做。
由于checksum方法所做的全部就是計(jì)算一個(gè)值,因此它并不需要顯式的return語(yǔ)句。另一種對(duì)方法的簡(jiǎn)寫(xiě)方式是,當(dāng)一個(gè)方法只會(huì)計(jì)算一個(gè)返回結(jié)果的表達(dá)式且這個(gè)表達(dá)式很短時(shí),(方法體)可以被放置在def的同一行。為了極致的精簡(jiǎn),還可以省略結(jié)果類(lèi)型,Scala會(huì)幫你推斷出來(lái)。做出這些修改之后,ChecksumAccumulator類(lèi)看上去是這樣的:

在前面的示例中,雖然Scala能夠正確地推斷出add和checksum這兩個(gè)方法的結(jié)果類(lèi)型,這段代碼的讀者也需要通過(guò)研讀方法體中的代碼“在腦海里推斷”(mentally infer)這些結(jié)果類(lèi)型。正因如此,通常更好的做法是對(duì)類(lèi)中聲明為公有的方法顯式地給出結(jié)果類(lèi)型,即使編譯器可以幫你推斷出來(lái)。示例4.1展示了這種風(fēng)格。
對(duì)于結(jié)果類(lèi)型為Unit的方法,如ChecksumAccumulator的add方法,其執(zhí)行目的是得到副作用。副作用通常是指改變方法外部的某種狀態(tài)或者執(zhí)行I/O的動(dòng)作。對(duì)本例的add方法而言,其副作用是給sum重新賦值。那些僅僅因?yàn)槠涓弊饔枚粓?zhí)行的方法被稱作過(guò)程(procedure)。

示例4.1 ChecksumAccumulator類(lèi)的最終版本
- Django+Vue.js商城項(xiàng)目實(shí)戰(zhàn)
- JavaScript+DHTML語(yǔ)法與范例詳解詞典
- Game Programming Using Qt Beginner's Guide
- 軟件測(cè)試項(xiàng)目實(shí)戰(zhàn)之性能測(cè)試篇
- Blender 3D Incredible Machines
- 小程序,巧運(yùn)營(yíng):微信小程序運(yùn)營(yíng)招式大全
- Java軟件開(kāi)發(fā)基礎(chǔ)
- ASP.NET 3.5程序設(shè)計(jì)與項(xiàng)目實(shí)踐
- Unreal Engine 4 Shaders and Effects Cookbook
- Asynchronous Android Programming(Second Edition)
- JavaCAPS基礎(chǔ)、應(yīng)用與案例
- C和C++游戲趣味編程
- 計(jì)算機(jī)應(yīng)用基礎(chǔ)案例教程
- Java SE實(shí)踐教程
- WordPress Search Engine Optimization(Second Edition)