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類別中,overridebuildingMaterial屬性並指派屬性的值為"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
- 執行程式碼,並留意
RoundTower2 個層樓的容量目前為 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()
- 首先,新增
abstractfloorArea()函式至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程式庫匯入功能