1. 事前準備
必要條件
- 熟悉使用 Kotlin Playground 編輯 Kotlin 程式。
- 如本課程單元 1 所述,瞭解 Kotlin 程式設計的基本概念。具體來說,您可學到
main()
程式、會傳回值、變數、資料類型和作業的含引數函式,以及if/else
陳述式。 - 您還可以在 Kotlin 中定義類別、從當中建立物件執行個體,以及存取類別的屬性和方法。
課程內容
- 建立 Kotlin 程式,並使用繼承實作類別階層。
- 擴充類別、覆寫現有的功能,並新增功能。
- 選擇變數適用的適當瀏覽權限修飾符。
建構項目
- Kotlin 程式包含做為類別階層實作的各種居所。
需求條件
- 一部能連上網際網路並存取 Kotlin Playground 的電腦
2. 類別階層是什麼?
使用者習慣為類似的屬性和行為項目分門別類,甚至排列一些類型的階層。舉例來說,在蔬菜等廣泛的類別下,可以細分為豆類等類型。豆類下可以再細分為豌豆、四季豆、扁豆、鷹嘴豆、大豆等。
而這可用階層表示,因為豆類包含或沿用蔬菜的所有屬性 (例如兩者均為可食用植物)。以此類推,豌豆、四季豆、扁豆都有豆類的屬性,外加豆類獨有的屬性。
以下說明如何以程式設計術語表示上述關係。如果在 Kotlin 中將 Vegetable
設為類別,即可建立 Legume
做為子項,或 Vegetable
類別的子類別。即 Legume
類別會繼承 (也可使用) Vegetable
類別所有的屬性和方法。
以下的類別階層圖表可顯示這項關係。您可以參照 Vegetable
做為 Legume
類別的父項或父類別。
您可以建立 Legume
的子類別 (例如 Lentil
和 Chickpea
),然後繼續展開類別階層。這會將 Legume
設為 Vegetable
的子項或子類別,同時也設為 Lentil
和 Chickpea
的父項或父類別。Vegetable
是這個階層的「根」層級或頂層 (或基礎) 類別。
Android 類別中的繼承
雖然不使用類別,也可以撰寫 Kotlin 程式碼,但在之前的程式碼研究室中,Android 以類別的格式提供很多組件,包括活動、檢視畫面和檢視區塊群組。所以,瞭解類別階層是 Android 應用程式開發的基礎,讓您可以善用 Android 架構提供的功能。
舉例來說,Android 的 View
類別代表螢幕上負責繪圖和事件處理的長方形區域。TextView
類別是 View
類別的子類別,代表 TextView
繼承 View
類別的所有屬性和功能,並新增向使用者顯示文字的特定邏輯。
更進一步來說,EditText
和 Button
類別是 TextView
類別的子項。這些類別繼承 TextView
和 View
類別所有的屬性和方法,並新增專屬的特定邏輯。例如,EditText
加入可編輯螢幕上文字的專屬功能。
EditText
可以直接將 TextView
類別加入子類別,同時將 View
類別加入子類別,不必在 EditText
類別複製及貼上 View
和 TextView
類別的所有邏輯。然後,EditText
類別中的程式碼可以致力讓 UI 元件不同於其他檢視畫面。
在 developers.android.com 網站 Android 類別的文件資訊頁面頂端上,即可找到類別階層圖。如果您在階層頂端看到 kotlin.Any
,原因是 Kotlin 中所有類別都有相同的父類別「Any」。請按這裡瞭解詳情。
如您所見,學習利用類別之間的繼承知識,可讓您更輕鬆地撰寫、重複使用、讀取並測試程式碼。
3. 建立基礎類別
居所類別階層
在本程式碼研究室中,您將建構 Kotlin 程式,該程式使用居所 (一般人起居的遮蔽處) 和樓層空間、樓層和居民做為範例,示範類別階層的運作方式。
以下是您要建構的類別階層圖。根層級的 Dwelling
會指定居所為 true 的屬性和功能 (類似於藍圖)。接著,您會看到方形小屋 (SquareCabin
)、圓型茅屋 (RoundHut
),以及圓塔 (RoundTower
,也就是多樓層的 RoundHut
)。
實作的類別:
Dwelling
:代表非特定遮蔽處的基本類別,包含所有居所通用資訊。SquareCabin
:正方形樓層面積的木製正方形小屋。RoundHut
:茅草建造的圓形茅屋,包含圓形樓層面積和RoundTower
的父項。RoundTower
:石頭建造的圓塔,包含圓形樓層面積和多個樓層。
建立抽象居所類別
任何類別都可以是類別階層的基本類別,或其他類別的父項。
「abstract」(抽象) 類別是因為未完整實作,無法執行個體化的類別。您可以將其視為草圖。草圖包含專案的靈感和規劃,但往往缺少資訊建構專案。請使用草圖 (抽象類別) 建立藍圖 (類別),然後建構實際的物件執行個體。
建立父類別常見的好處是,包含所有子類別通用的屬性和函式。如果不知道屬性的值和函式的實作,請將類別設為抽象。舉例來說,Vegetables
包含許多所有蔬菜通用的屬性,但您無法為非特定蔬菜建立執行個體,因為您沒有具體資訊,例如形狀或顏色。因此,Vegetable
是抽象類別,由子類別確定每種蔬菜的特定詳細資料。
抽象類別的宣告是以 abstract
關鍵字做為開頭。
Dwelling
是如 Vegetable
的抽象類別,它會包含很多類型的居所通用的屬性和函式,但不知道屬性確切的值和函式實作的詳細資料。
- 前往 Kotlin Playground:https://developer.android.com/training/kotlinplayground。
- 在編輯器中,刪除
main()
函式中的println("Hello, world!")
。 - 然後在
main()
函式下方加入以下程式碼,建立名為Dwelling
的abstract
類別。
abstract class Dwelling(){
}
新增建材的屬性
在這個 Dwelling
類別中,您可以定義所有居所為 true 的建材,即使建材會因不同的居所而異。所有居所都使用建材建構。
- 在
Dwelling
中,建立類型String
的buildingMaterial
變數以代表建材。因為建材不會變更,請使用val
做為建材不可變的變數。
val buildingMaterial: String
- 執行程式後,您會看到以下錯誤訊息。
Property must be initialized or be abstract
buildingMaterial
屬性沒有值。其實您「無法」指定值,因為非特定的建築物不會使用任何特定建材。所以如錯誤訊息所示,您可以在 buildingMaterial
的宣告中前置 abstract
關鍵字,表示不會在這裡定義此屬性。
- 在變數定義中加入
abstract
關鍵字。
abstract val buildingMaterial: String
- 執行程式碼時,只要執行不出現任何錯誤,編譯程式碼就不會出現錯誤。
- 在
main()
函式中建立Dwelling
的執行個體,並執行程式碼。
val dwelling = Dwelling()
- 您看到錯誤訊息的原因是,無法建立抽象
Dwelling
類別的執行個體。
Cannot create an instance of an abstract class
- 刪除不正確的程式碼。
您目前完成的程式碼:
abstract class Dwelling(){
abstract val buildingMaterial: String
}
新增容量屬性
居所的另一個屬性是居住的人數。
所有居所的容量都不會變更。但 Dwelling
父類別中無法設定容量。如果是特定類型的居所,請在子類別定義容量。
- 在
Dwelling
中,新增名為capacity
的abstract
整數val
。
abstract val capacity: Int
新增居民數目的私有屬性
所有居所都有很多 residents
居住在其中 (數目可能小於或等於 capacity
),所以為了繼承並使用屬性,請定義所有子類別 Dwelling
中的 residents
屬性。
- 您可以將
residents
參數傳遞至Dwelling
類別建構函式。residents
屬性為var
,因為建立執行個體後,可以變更居民數目。
abstract class Dwelling(private var residents: Int) {
請注意,residents
屬性會標示 private
關鍵字。Private (私有) 是 Kotlin 的可見度修飾符,即只有這個類別能看到 (並使用) residents
屬性。程式其他位置無法存取此屬性。您可以使用 private 關鍵字標示屬性或方法。否則,在未指定瀏覽權限修飾符時,這些屬性和方法會預設為 public
,並可透過程式的其他位置存取。有鑑於居所的居民數目通常是私人資訊 (相對於建材或建築物容量),這樣的決定很合理。
使用居所的 capacity
和目前 residents
定義的數目,您可以建立函式 hasRoom()
,判斷居所是否空房容納其他居民。您可以在 Dwelling
類別中定義並實作 hasRoom()
函式,因為計算所有居所是否有空房,使用的是相同的公式。如果 residents
的數目小於 capacity
,Dwelling
中會顯示空房,然後此函式會根據比較值傳回 true
或 false
。
- 新增
hasRoom()
函式至Dwelling
類別。
fun hasRoom(): Boolean {
return residents < capacity
}
- 請執行這個程式碼,此程式碼不該出現錯誤。目前未出現任何動作。
完成的程式碼如下:
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
4. 建立子類別
建立 SquareCabin 子類別
- 在
Dwelling
類別下方,建立名為SquareCabin
的類別。
class SquareCabin
- 接著,您必須指出
SquareCabin
和Dwelling
的關連。在程式碼中,您要指出SquareCabin
是Dwelling
的延伸模組 (或Dwelling)
的子類別,因為SquareCabin
會提供Dwelling
抽象部分的實作)。
若要指出繼承關係,請在 SquareCabin
類別名稱後方加上半形冒號 (:
),然後呼叫並初始化父項 Dwelling
類別。別忘了在 Dwelling
類別名稱後方加上括號。
class SquareCabin : Dwelling()
- 從父類別延伸時,您必須傳入父類別必要的參數。
Dwelling
要求residents
數目做為輸入內容。您可以傳入居民的固定數目 (例如3
)。
class SquareCabin : Dwelling(3)
但若要程式更有彈性,並可以使用 SquareCabins
居民的變數數目,請在 SquareCabin
類別定義中,將 residents
設為參數。請勿將 residents
宣告為 val,
,因為您會重複使用父項類別 Dwelling
中宣告的屬性。
class SquareCabin(residents: Int) : Dwelling(residents)
- 執行程式碼。
- 這會導致錯誤發生。一探究竟:
Class 'SquareCabin' is not abstract and does not implement abstract base class member public abstract val buildingMaterial: String defined in Dwelling
宣告抽象函式和變數和 promise 一樣,您會在之後提供值並實作。以變數來說,即代表您必須提供值給抽象類別的子類別。以函式來說,即子類別必須實作函式主體。
在 Dwelling
類別中,您已定義 abstract
變數為 buildingMaterial
。SquareCabin
是 Dwelling
的子類別,所以必須提供 buildingMaterial
的值。使用 override
關鍵字,指出父項類別已定義這個屬性,並會在此類別中覆寫。
- 在
SquareCabin
類別中,override
buildingMaterial
屬性並指派屬性的值為"Wood"
。 - 對
capacity
執行相同動作,例如 6 位居民可以住在SquareCabin
。
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
完成的程式碼應如下所示:
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
若要測試程式碼,請在程式中建立 SquareCabin
的執行個體。
使用 SquareCabin
- 在
Dwelling
和SquareCabin
類別定義前,插入空白的main()
函式。
fun main() {
}
abstract class Dwelling(private var residents: Int) {
...
}
class SquareCabin(residents: Int) : Dwelling(residents) {
...
}
- 在
main()
函式中,建立容納 6 位居民,名為squareCabin
的SquareCabin
執行個體。新增建材、容量的輸出陳述式,以及hasRoom()
函式。
fun main() {
val squareCabin = SquareCabin(6)
println("\nSquare Cabin\n============")
println("Capacity: ${squareCabin.capacity}")
println("Material: ${squareCabin.buildingMaterial}")
println("Has room? ${squareCabin.hasRoom()}")
}
請注意,在 SquareCabin
類別中未定義 hasRoom()
函式,但在 Dwelling
類別中為已定義。因為 SquareCabin
是 Dwelling
類別的子類別,所以會無條件繼承 hasRoom()
函式。SquareCabin
的所有執行個體目前已可呼叫 hasRoom()
函式,如程式碼片段 squareCabin.hasRoom()
所示。
- 執行程式碼,程式碼會輸出以下內容:
Square Cabin ============ Capacity: 6 Material: Wood Has room? false
您已建立容納 6
位居民 (亦等於 capacity
) 的 squareCabin
,因此 hasRoom()
會傳回 false
。使用較少的 residents
嘗試初始化 SquareCabin
,並再次執行程式後,hasRoom()
會傳回 true
。
使用 with 簡化程式碼
在 println()
陳述式中,每次參考 squareCabin
的屬性或函式時,請注意您必須重複 squareCabin.
的原因。因為複製及貼上輸出陳述式時,可能會發生錯誤。
使用類別的特定執行個體,且必須存取該執行個體的多個屬性和函式時,可以使用 with
陳述式說明:「對這個執行個體物件執行下列所有作業」。以關鍵字 with
開頭,接著是括號中的執行個體名稱,後面加上大括號,包含您要執行的作業。
with (instanceName) {
// all operations to do with instanceName
}
- 在
main()
函式中,將輸出陳述式變更為使用with
。 - 刪除輸出陳述式中的
squareCabin.
。
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
- 再次執行程式碼,確認程式碼可正常執行,並顯示相同的輸出內容。
Square Cabin ============ Capacity: 6 Material: Wood Has room? false
以下是已完成的程式碼:
fun main() {
val squareCabin = SquareCabin(6)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
建立 RoundHut 子類別
- 與
SquareCabin
使用相同的方式,新增另一個子類別 (RoundHut
) 至Dwelling
。 - 覆寫
buildingMaterial
並指定為"Straw"
的值。 - 覆寫
capacity
並設為 4。
class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- 在
main()
中,建立容納 3 位居民的RoundHut
執行個體。
val roundHut = RoundHut(3)
- 加入下方的程式碼,即可輸出
roundHut
的資訊。
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
- 執行程式碼,整個程式的輸出內容應為:
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Round Hut ========= Material: Straw Capacity: 4 Has room? true
您目前的類別階層如下所示,其中 Dwelling
是根層級類別,而 SquareCabin
和 RoundHut
是 Dwelling
的子類別。
建立 RoundTower 子類別
這個類別階層最後一個類別是圓塔。圓塔就像是石頭建造、多個樓層的圓形小屋。所以您可以將 RoundTower
設為 RoundHut
的子類別。
- 建立
RoundTower
類別,即RoundHut
的子類別。將residents
參數新增至RoundTower
的建構函式,然後將該參數傳遞至RoundHut
父類別的建構函式。 - 將
buildingMaterial
覆寫為"Stone"
。 - 將
capacity
設為4
。
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- 執行這個程式碼後,您會看到錯誤訊息。
This type is final, so it cannot be inherited from
這個錯誤代表無法將 RoundHut
類別設為子類別 (或繼承該類別)。根據預設,在 Kotlin 中,類別為最終類別,且無法加入子類別。只能繼承 abstract
類別,或標示 open
關鍵字的類別。因此,您必須以 open
關鍵字標示 RoundHut
類別,才能繼承類別。
- 請在
RoundHut
宣告開頭加入open
關鍵字。
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- 在
main()
中,建立roundTower
的執行個體,並輸出相關資訊。
val roundTower = RoundTower(4)
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
以下是完整程式碼。
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- 執行程式碼。程式碼應該可以正常執行,並產生下列輸出內容。
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Round Hut ========= Material: Straw Capacity: 4 Has room? true Round Tower ========== Material: Stone Capacity: 4 Has room? false
新增多個樓層至 RoundTower
RoundHut
(圓形茅屋) 是單層建築物。塔通常是多層 (樓層)。
想一下容量,塔的樓層越多,容量就越多。
您可以修改 RoundTower
為多個樓層,並根據樓層數調整容量。
- 更新
RoundTower
建構函式,並針對樓層數採用額外的整數參數val floors
。在residents
後方置放參數。請注意,您不必傳遞參數至父項RoundHut
建構函式,因為在RoundTower
和RoundHut
中定義的floors
是沒有floors
。
class RoundTower(
residents: Int,
val floors: Int) : RoundHut(residents) {
...
}
- 執行程式碼。在
main()
方法中建立roundTower
時發生錯誤,因為您未提供floors
引數的數字。您可以新增遺漏的引數。
您也可以在 RoundTower
的類別定義中,新增 floors
的預設值,如下所示。接著,如果 floors
的值未傳遞至建構函式,您可以使用預設值來建立物件執行個體。
- 在程式碼中,在
floors
的宣告後方加上= 2
,即可指派預設值 2。
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
...
}
- 執行程式碼。程式碼目前應該可以編譯,因為
RoundTower(4)
已可使用 2 個樓層的預設值,建立RoundTower
物件執行個體。 - 在
RoundTower
類別中,更新capacity
即可乘以樓層數。
override val capacity = 4 * floors
- 執行程式碼,並留意
RoundTower
2 個層樓的容量目前為 8。
以下是已完成的程式碼。
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
}
5. 修改階層中的類別
計算樓層面積
在本練習中,您將瞭解在抽象類別中,如何宣告抽象函式,並在子類別實作函式。
所有居所都有樓層面積,但根據居所的形狀,計算方式會有所不同。
定義居所類別的 floorArea()
- 首先,新增
abstract
floorArea()
函式至Dwelling
類別。傳回Double
。Double 是資料類型 (例如String
和Int
),並用於浮點數目,即包含小數點與其後小數部分的數字 (例如 5.8793)。
abstract fun floorArea(): Double
抽象類別中定義的所有抽象方法必須在其子類別中實作。執行程式碼前,您必須在子類別中實作 floorArea()
。
實作 SquareCabin 的 floorArea()
和 buildingMaterial
和 capacity
一樣,既然要實作父項類別定義的 abstract
函式,就必須使用 override
關鍵字。
- 在
SquareCabin
類別中,以關鍵字override
做為開頭,接著實際實作floorArea()
函式,如下所示。
override fun floorArea(): Double {
}
- 傳回計算的樓層面積。長方形或正方形的面積是邊長乘以另一個邊長。函式主體是
return length * length
。
override fun floorArea(): Double {
return length * length
}
長度不是類別中的變數,也與所有執行個體不同,所以您可以新增長度,做為 SquareCabin
類別的建構函式參數。
- 變更
SquareCabin
的類別定義,並新增Double
類型的length
參數。請宣告屬性為val
,因為建築物的長度不變。
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
所以 Dwelling
和所有子類別都包含 residents
,做為建構函式的引數。因為這是 Dwelling
建構函式的第一個引數,所以最佳做法是將該引數設為所有子類別建構函式的第一個引數,並以相同的順序在所有類別定義中放置引數。因此,請在 residents
參數後方插入新的 length
參數。
- 在
main()
中更新squareCabin
執行個體的建立作業。傳遞50.0
至SquareCabin
建構函式,做為length
。
val squareCabin = SquareCabin(6, 50.0)
- 在
squareCabin
的with
陳述式中,新增樓層面積的輸出陳述式。
println("Floor area: ${floorArea()}")
程式碼目前不執行的原因是,RoundHut
中也必須實作 floorArea()
。
實作 RoundHut 的 floorArea()
請以相同方式,實作 RoundHut
的樓層面積。RoundHut
也是 Dwelling
的直接子類別,所以您必須使用 override
關鍵字。
圓形居所的樓層面積是 PI * 半徑 * 半徑。
PI
是數學值,由數學程式庫定義。程式庫是程式外部定義的函式和數的集合,可供程式使用。若要使用程式庫的函式或值,您必須說明編譯器要使用的函式或值。方法是在程式匯入函式或值。若要在程式中使用 PI
,請匯入 kotlin.math.PI
。
- 從 Kotlin 數學程式庫匯入
PI
。在檔案頂端的main()
前放置下列內容。
import kotlin.math.PI
- 實作
RoundHut
的floorArea()
函式。
override fun floorArea(): Double {
return PI * radius * radius
}
警告:如果您未匯入 kotlin.math.PI,就會收到錯誤訊息,因此必須先匯入這個程式庫才能使用。或者,您可以撰寫 PI 的完整版本,例如 kotlin.math.PI * radius * radius,則不需要匯入陳述式。
- 更新
RoundHut
建構函式,並傳入radius
。
open class RoundHut(
residents: Int,
val radius: Double) : Dwelling(residents) {
- 在
main()
中,傳遞10.0
的radius
至RoundHut
建構函式,即可更新roundHut
的初始化作業。
val roundHut = RoundHut(3, 10.0)
- 在
roundHut
的with
陳述式中,加入輸出陳述式。
println("Floor area: ${floorArea()}")
實作 RoundTower 的 floorArea()
程式碼未執行,並因為下列錯誤失敗:
Error: No value passed for parameter 'radius'
在 RoundTower
中,若要程式順利編譯,其實不必實作 floorArea()
,因為圓塔會繼承 RoundHut
的樓層面積,但為了使用相同的 radius
引數做為其父項 RoundHut
,您必須更新 RoundTower
類別定義。
- 變更 RoundTower 的建構函式,讓其也接受
radius
。在residents
後方和floors
前放置radius
。建議您使用結尾列出包含預設值的變數。請記得傳遞radius
至父項類別建構函式。
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
- 更新
main()
中的roundTower
初始化作業。
val roundTower = RoundTower(4, 15.5)
- 然後加入呼叫
floorArea()
的輸出陳述式。
println("Floor area: ${floorArea()}")
- 您現在可以執行程式碼了!
- 請注意,
RoundTower
的計算不正確,因為圓塔繼承RoundHut
,所以不會計算floors
的數目。 - 在
RoundTower
中,override floorArea()
提供不同的實作,讓面積可以乘以樓層數。請注意,您可以在抽象類別 (Dwelling
) 中定義函式、在子類別 (RoundHut
) 中實作函式,然後在子類別 (RoundTower
) 的子類別中,再次覆寫該函式。這是兩個類別的最佳做法,您可以繼承需要的功能,並覆寫不必要的功能。
override fun floorArea(): Double {
return PI * radius * radius * floors
}
這個程式碼可以使用,但 RoundHut
父項類別中,已有方法可以避免重複的程式碼。您可以從父類別 RoundHut
呼叫 floorArea()
函式,該函式會傳回 PI * radius * radius
。然後再將此結果乘以 floors
的數目。
- 在
RoundTower
中,更新floorArea()
並使用floorArea()
的父類別實作。使用super
關鍵字呼叫父項中定義的函式。
override fun floorArea(): Double {
return super.floorArea() * floors
}
- 再次執行程式碼,然後
RoundTower
會輸出多個樓層的正確樓層空間。
以下是已完成的程式碼:
import kotlin.math.PI
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
abstract fun floorArea(): Double
}
class SquareCabin(residents: Int,
val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
override fun floorArea(): Double {
return length * length
}
}
open class RoundHut(residents: Int,
val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
override fun floorArea(): Double {
return PI * radius * radius
}
}
class RoundTower(residents: Int, radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
輸出內容應如下所示:
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Floor area: 2500.0 Round Hut ========= Material: Straw Capacity: 4 Has room? true Floor area: 314.1592653589793 Round Tower ========== Material: Stone Capacity: 8 Has room? true Floor area: 1509.5352700498956
允許新居民取得房間
新增功能,使用 getRoom()
函式增加 1 位居民的數目,讓新居民取得房間。這個邏輯適用所有居所,所以您可以在 Dwelling
中實作這個函式,同時所有子類別和其子項都可使用這個函式。真棒!
注意:
- 使用
if
陳述式,只在有容量剩餘時,才能新增居民。 - 輸出結果訊息。
- 您可以使用
residents++
做為新增 1 至residents
變數的函式residents = residents + 1
簡寫。
- 在
Dwelling
類別中實作getRoom()
函式。
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
- 新增一些輸出陳述式至
roundHut
的with
陳述式區塊,並觀察搭配使用getRoom()
和hasRoom()
的變化。
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
這些輸出陳述式的輸出內容:
Has room? true You got a room! Has room? false Sorry, at capacity and no rooms left.
詳情請參閱解決方案程式碼。
調整圓形居所的地毯
假設您需要知道 RoundHut
和 RoundTower
的地毯側邊長度是多少。在 RoundHut
放置函式,供所有圓形居所使用。
- 首先,從
kotlin.math
程式庫匯入sqrt()
函式。
import kotlin.math.sqrt
- 在
RoundHut
類別中實作calculateMaxCarpetLength()
函式。以下公式可以計算置於圓形居所的方形地毯長度:sqrt(2) * radius
。詳情請見上圖。
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
將 Double
值 2.0
傳至數學函式 sqrt(2.0)
,因為函式的傳回類型為 Double
,而非Integer
。
RoundHut
和RoundTower
執行個體目前已可呼叫calculateMaxCarpetLength()
方法。新增輸出陳述式至main()
函式中的roundHut
和roundTower
。
println("Carpet Length: ${calculateMaxCarpetLength()}")
詳情請參閱解決方案程式碼。
恭喜!您已建立包含屬性和函式的完整類別階層,並瞭解如何建立更實用的類別!
6. 解決方案程式碼
以下是本程式碼研究室的完整解決方案程式碼,包括相關註解。
/**
* Program that implements classes for different kinds of dwellings.
* Shows how to:
* Create class hierarchy, variables and functions with inheritance,
* abstract class, overriding, and private vs. public variables.
*/
import kotlin.math.PI
import kotlin.math.sqrt
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
println("Carpet size: ${calculateMaxCarpetLength()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Carpet Length: ${calculateMaxCarpetLength()}")
}
}
/**
* Defines properties common to all dwellings.
* All dwellings have floorspace,
* but its calculation is specific to the subclass.
* Checking and getting a room are implemented here
* because they are the same for all Dwelling subclasses.
*
* @param residents Current number of residents
*/
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
/**
* Calculates the floor area of the dwelling.
* Implemented by subclasses where shape is determined.
*
* @return floor area
*/
abstract fun floorArea(): Double
/**
* Checks whether there is room for another resident.
*
* @return true if room available, false otherwise
*/
fun hasRoom(): Boolean {
return residents < capacity
}
/**
* Compares the capacity to the number of residents and
* if capacity is larger than number of residents,
* add resident by increasing the number of residents.
* Print the result.
*/
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
}
/**
* A square cabin dwelling.
*
* @param residents Current number of residents
* @param length Length
*/
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
/**
* Calculates floor area for a square dwelling.
*
* @return floor area
*/
override fun floorArea(): Double {
return length * length
}
}
/**
* Dwelling with a circular floorspace
*
* @param residents Current number of residents
* @param radius Radius
*/
open class RoundHut(
residents: Int, val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
/**
* Calculates floor area for a round dwelling.
*
* @return floor area
*/
override fun floorArea(): Double {
return PI * radius * radius
}
/**
* Calculates the max length for a square carpet
* that fits the circular floor.
*
* @return length of square carpet
*/
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
}
/**
* Round tower with multiple stories.
*
* @param residents Current number of residents
* @param radius Radius
* @param floors Number of stories
*/
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
// Capacity depends on the number of floors.
override val capacity = floors * 4
/**
* Calculates the total floor area for a tower dwelling
* with multiple stories.
*
* @return floor area
*/
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
7. 匯總
在本程式碼研究室中,您瞭解如何:
- 建立類別階層 (即類別的樹狀結構),以及子項會繼承父項類別的功能。子類別會繼承屬性和函式。
- 建立
abstract
類別,其子類別會實作其餘的部分功能。所以abstract
類別無法執行個體化。 - 建立
abstract
類別的子類別。 - 使用
override
關鍵字覆寫子類別的屬性和函式。 - 使用
super
關鍵字參考父項類別的函式和屬性。 - 建立類別
open
,即可加入子類別。 - 建立屬性
private
,然後只能在類別中使用該屬性。 - 使用
with
建構函式,多次呼叫相同的物件執行個體。 - 從
kotlin.math
程式庫匯入功能