本程式碼研究室是程式設計課程 Kotlin 新手上路課程的一部分。使用程式碼研究室逐步完成程式碼課程後,您將能充分發揮本課程的潛能。視您的知識而定,您或許可以略過某些部分。本課程的適用對象為熟悉物件導向語言,且想要學習 Kotlin 的程式設計人員。
簡介
在這個程式碼研究室,您可以建立 Kotlin 程式,並學習 Kotlin 中的類別和物件。如果您知道另一種物件導向語言,大部分內容對您來說就不陌生,但 Kotlin 有一些重要差異,可以減少您需要撰寫的程式碼數量。此外,您也會瞭解抽象類別和介面委派。
本課程並非只建立一個範例應用程式,而是用來建立您的知識,但彼此之間互不相關,因此您可以熟悉自己熟悉的部分。許多產品是透過水族箱主題進行連結。如果您想看完整的水族箱故事,請參考程式設計程式 Kotlin 新手上路課程。
須知事項
- Kotlin 基本概念,包括類型、運算子和迴圈
- Kotlin's 函式語法
- 物件導向程式設計的基本概念
- IDE 的基本概念,例如 IntelliJ IDEA 或 Android Studio
課程內容
- 如何在 Kotlin 中建立類別及存取屬性
- 如何在 Kotlin 中建立及使用類別建構函式
- 如何建立子類別,以及沿用的運作方式
- 關於抽象類別、介面和介面委派
- 如何建立及使用資料類別
- 如何使用單調、列舉和密封類別
執行步驟
- 建立具有屬性的類別
- 建立類別的建構函式
- 建立子類別
- 查看抽象類別和介面的範例
- 建立簡單的資料類別
- 瞭解單調、列舉和密封類別
您應該已經熟悉以下的計劃條款:
- 類別是物件的藍圖。舉例來說,
Aquarium
類別是製作水族館物件的藍圖。 - 物件是類別的執行個體,水族箱物件則是一個實際的
Aquarium
。 - 屬性是類別的特徵,例如
Aquarium
的長度、寬度與高度。 - Methods (也稱為 member 函式) 是 類別的功能。方法就是您能夠「做到」的事物。例如,您可以
fillWithWater()
Aquarium
物件。 - 「介面」是指類別可實作的規格。舉例來說,水族箱以外的物品常有清潔,而一般用不同方式處理的方式通常也不一樣。因此,您可能有一個稱為
Clean
的介面,用於定義clean()
方法。Aquarium
類別可實作Clean
介面以使用海綿來清潔水族箱。 - 套件可用來將相關的程式碼分組,讓內容井然有序,或建立程式碼庫。建立套件後,您可以將套件的內容匯入其他檔案,並重複使用當中的程式碼和類別。
在這項工作中,您會建立一個新的套件,以及包含一些屬性和 方法的類別。
步驟 1:建立套件
套件可協助您整理程式碼。
- 在「Project」(專案) 窗格中,於「Hello Kotlin」專案底下的 [src] 資料夾上按一下滑鼠右鍵。
- 選取 [New > Package] 並呼叫
example.myapp
。
步驟 2:建立含有屬性的類別
類別是透過關鍵字 class
定義,而一般按照類別名稱開頭加上大寫字母。
- 在 example.myapp 套件上按一下滑鼠右鍵。
- 選取 [New > Kotlin File / Class]。
- 在「Kind」下方,選取 [Class],並將類別命名為
Aquarium
。IntelliJ IDEA 包含檔案中的套件名稱,並為您建立空白的Aquarium
類別。 - 在
Aquarium
類別中,定義並初始化寬度、高度和長度 (單位為公分) 的var
屬性。使用預設值初始化屬性。
package example.myapp
class Aquarium {
var width: Int = 20
var height: Int = 40
var length: Int = 100
}
實際上,Kotlin 會自動為您在 Aquarium
類別中定義的屬性建立 getter 和 setter,方便您直接存取屬性,例如 myAquarium.length
。
步驟 3:建立 main() 函式
建立名為 main.kt
的新檔案來存放 main()
函式。
- 在左側的「Project」(專案) 窗格中,對 example.myapp 套件按一下滑鼠右鍵。
- 選取 [New > Kotlin File / Class]。
- 在「Kind」下拉式選單中,保留選取項目 [File],並將檔案命名為
main.kt
。IntelliJ IDEA 包含套件名稱,但不包含檔案的類別定義。 - 定義
buildAquarium()
函式,然後在內部建立Aquarium
的執行個體。如要建立執行個體,請參照此類別 (即Aquarium()
函式)。這會呼叫類別的建構函式,並建立Aquarium
類別的例項,類似在其他語言中使用new
。 - 定義
main()
函式並呼叫buildAquarium()
。
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium()
}
fun main() {
buildAquarium()
}
步驟 4:新增方法
- 在
Aquarium
類別中,新增一個可用於列印水族箱尺寸屬性的方法。
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
}
- 在
main.kt
中,在buildAquarium()
上呼叫myAquarium
上的printSize()
方法。
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
}
- 按一下
main()
函式旁邊的綠色三角形,即可執行程式。觀察結果。
⇒ Width: 20 cm Length: 100 cm Height: 40 cm
- 在
buildAquarium()
中新增程式碼,將高度設為 60 並列印更改過的維度屬性。
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
myAquarium.height = 60
myAquarium.printSize()
}
- 執行程式並觀察輸出結果。
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 100 cm Height: 60 cm
在這項工作中,您會建立類別的建構函式,並繼續使用屬性。
步驟 1:建立建構函式
在這個步驟中,您會為第一項工作建立的 Aquarium
類別新增建構函式。在先前的範例中,Aquarium
的每個例項都具有相同的維度。您可以在設定屬性後予以變更,但建立維度時要先使用正確的大小就會比較簡單。
在部分程式設計語言中,建構函式是透過與類別名稱相同的方法建立方法。在 Kotlin 中,您直接在類別宣告中定義建構函式,將括號中的參數指定為類別的 方法。如同 Kotlin 中的函式,這些參數可以包含預設值。
- 在您先前建立的
Aquarium
類別中,變更類別定義,加入三個建構函式參數,其中包含length
、width
和height
的預設值,並將這些值指派給相應的屬性。
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
// Dimensions in cm
var length: Int = length
var width: Int = width
var height: Int = height
...
}
- 更精簡的 Kotlin 方法是使用
var
或val
直接使用建構函式定義屬性,而 Kotlin 也會自動建立 getter 和 setter。之後就可以移除類別內文中的屬性定義。
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
- 使用該建構函式建立
Aquarium
物件時,您可以指定任何引數並取得預設值,也可以僅指定部分引數,也可以指定所有的引數,並建立完全自訂大小的Aquarium
。在buildAquarium()
函式中,您可以嘗試透過不同名稱建立參數來建立Aquarium
物件。
fun buildAquarium() {
val aquarium1 = Aquarium()
aquarium1.printSize()
// default height and length
val aquarium2 = Aquarium(width = 25)
aquarium2.printSize()
// default width
val aquarium3 = Aquarium(height = 35, length = 110)
aquarium3.printSize()
// everything custom
val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
aquarium4.printSize()
}
- 執行程式並觀察輸出內容。
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 25 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 110 cm Height: 35 cm Width: 25 cm Length: 110 cm Height: 35 cm
請注意,您不一定要為建構函式超載,並針對每個版本撰寫不同的版本 (多選一些組合)。Kotlin 會使用預設值和已命名的參數建立所需的資料。
步驟 2:新增初始化區塊
上方的建構函式建構函式會宣告屬性,並指派運算式的值給它們。如果您的建構函式需要更多初始化程式碼,可以將它放在一或多個 init
區塊中。在此步驟中,您將部分 init
區塊新增至 Aquarium
類別。
- 在
Aquarium
類別中,新增init
區塊以列印該物件正在初始化,新增第二個區塊,以列印每公升的音量。
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
init {
println("aquarium initializing")
}
init {
// 1 liter = 1000 cm^3
println("Volume: ${width * length * height / 1000} l")
}
}
- 執行程式並觀察輸出內容。
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm
請注意,init
區塊的運作順序會依照類別定義中的順序執行,而當您呼叫建構函式時,系統會執行所有區塊。
步驟 3:瞭解次要建構函式
在這個步驟中,您將瞭解次要建構函式,並新增至類別。除了主要的建構函式可以含有一或多個 init
區塊之外,Kotlin 類別也可以含有一或多個次要建構函式,以允許建構函式超載,也就是具有不同引數的建構函式。
- 在
Aquarium
類別中,使用constructor
關鍵字新增次要建構函式做為多個引數的引數。根據魚類的數量,為該水族箱計算val
的水箱屬性 (以公升為單位)。每個魚撥水 2 升(2,000 cm^3),外加一個額外的空氣,所以水不溢出。
constructor(numberOfFish: Int) : this() {
// 2,000 cm^3 per fish + extra room so water doesn't spill
val tank = numberOfFish * 2000 * 1.1
}
- 在次要建構函式中,維持長度與寬度 (在主要建構函式中所設定) 相同,並計算讓容器進行指定水箱所需的高度。
// calculate the height needed
height = (tank / (length * width)).toInt()
- 在
buildAquarium()
函式中新增呼叫,以使用新的次要建構函式建立Aquarium
。列印相片的大小和音量。
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
- 執行程式並觀察輸出結果。
⇒ aquarium initializing Volume: 80 l Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
請注意,磁碟區會列印兩次,一次由主要建構函式中的 init
區塊執行,再執行次要建構函式,一次由 buildAquarium()
中的程式碼產生一次。
您也可以在主要建構函式中加入 constructor
關鍵字,但大多數情況下並不需要。
步驟 4:新增屬性 getter
在這個步驟中,您會加入明確的屬性 getter。Kotlin 會在您定義屬性時自動定義 getter 和 setter,但有時需要調整或計算屬性值。例如,您在上方列印了「Aquarium
」的音量。您可以定義變數和 getter,藉此將磁碟區設為屬性。由於必須計算 volume
,因此 getter 必須傳回計算值,您可以使用單行函式來執行這個動作。
- 在
Aquarium
類別中,定義名為volume
的Int
屬性,並定義計算下一行音量的get()
方法。
val volume: Int
get() = width * height * length / 1000 // 1000 cm^3 = 1 l
- 移除印出磁碟區的
init
區塊。 - 移除印有磁碟區的
buildAquarium()
代碼。 - 在
printSize()
方法中,新增線條以列印磁碟區。
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm "
)
// 1 l = 1000 cm^3
println("Volume: $volume l")
}
- 執行程式並觀察輸出結果。
⇒ aquarium initializing Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
尺寸和磁碟區與先前相同,但是在主要建構函式和次要建構函式完全初始化物件之後,磁碟區只會列印一次。
步驟 5:新增屬性集
在這個步驟中,您會為磁碟區建立新的屬性集。
- 在
Aquarium
類別中,將volume
變更為var
,使其可重複設定。 - 在 getter 的下方新增
set()
方法,為volume
屬性新增設定器,依據提供的水量重新計算高度。按照慣例,setter 參數的名稱是value
,不過您也可以視需要變更。
var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- 在
buildAquarium()
中新增程式碼,將水族箱的音量設為 70 公升。列印新的尺寸。
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
aquarium6.volume = 70
aquarium6.printSize()
}
- 再次執行程式,觀察變更後的高度和音量。
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm
Volume: 70 l
目前程式碼中沒有可見的修飾符,例如 public
或 private
。因為根據預設,Kotlin 中的所有資料都是公開的,因此可以存取任何位置,包括類別、方法、屬性以及成員變數。
在 Kotlin 中,類別、物件、介面、建構函式、函式、屬性和其設定器可能具備顯示設定修飾符:
public
表示課程外的顯示設定。所有項目預設為公開,包含類別的變數和方法。internal
表示該模組只會在該模組內顯示。「模組」是一組編譯的 Kotlin 檔案,例如程式庫或應用程式。private
表示該類別僅會顯示在該類別 (如果您使用函式時則為來源檔案)。- 「
protected
」與「private
」相同,但所有子類別都可以看到這個類別。
詳情請參閱 Kotlin 文件中的瀏覽權限修改工具。
成員變數
類別或成員變數中的屬性預設為 public
。如果您透過 var
定義這些變數,其可變動,亦即具有可讀性與可寫入。如果您使用 val
定義這些變數,這些變數在初始化後為唯讀狀態。
如需程式碼可讀取或寫入的資源,但外部程式碼只能讀取,您可以將屬性及其 getter 設為公開,並宣告設定器為私人,如下所示。
var volume: Int
get() = width * height * length / 1000
private set(value) {
height = (value * 1000) / (width * length)
}
在這項工作中,您將瞭解子類別和繼承在 Kotlin 中的運作方式。這些語言與你在其他語言中瀏覽的內容類似,但仍有一些差異。
在 Kotlin 中,類別預設為子類別。同樣地,屬性和成員變數也無法透過子類別覆寫 (雖然可供存取)。
您必須將類別標記為 open
,才能將它設為子類別。同樣地,您必須將屬性和成員變數標示為 open
,才能在子類別中覆寫這些變數。我們要求使用 open
關鍵字,以避免意外在類別的介面上洩漏實作的詳細資訊。
步驟 1:開啟水族館課程
在這個步驟中,您會將 Aquarium
類別設為 open
,以便在下一個步驟中覆寫此類別。
- 以
open
關鍵字標記Aquarium
類別及其所有屬性。
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
open var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- 新增值為
"rectangle"
的開放式shape
屬性。
open val shape = "rectangle"
- 添加一個打開的
water
包含的 getter 和返回的Aquarium
卷的 90% 的 getter。
open var water: Double = 0.0
get() = volume * 0.9
- 將代碼加入
printSize()
中即可列印形狀,以及測量體積的百分比。
fun printSize() {
println(shape)
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
// 1 l = 1000 cm^3
println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
- 在
buildAquarium()
中,變更程式碼來建立具有width = 25
、length = 25
和height = 40
的Aquarium
。
fun buildAquarium() {
val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
aquarium6.printSize()
}
- 執行程式並觀察新的輸出內容。
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full)
步驟 2:建立子類別
- 建立名為
Aquarium
的子類別,這個子類別會導入圓形圓柱槽,而非矩形槽。您可以在Aquarium
下方加入TowerTank
,因為您可以在另一個類別中加入與Aquarium
類別相同的類別。 - 在
TowerTank
中,覆寫建構函式中定義的height
屬性。若要覆寫屬性,請使用子類別中的override
關鍵字。
- 讓
TowerTank
的建構函式使用diameter
。在Aquarium
父類別中呼叫建構函式時,請針對diameter
和width
使用diameter
。
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
- 覆寫磁碟區屬性以計算氣瓶。圓柱的公式是半徑的平方,再乘以高。您必須從
java.lang.Math
匯入常數PI
。
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
- 在
TowerTank
中,將water
屬性覆寫為磁碟區的 80%。
override var water = volume * 0.8
- 將
shape
覆寫為"cylinder"
。
override val shape = "cylinder"
- 最後的
TowerTank
類別應如下所示。
Aquarium.kt
:
package example.myapp
import java.lang.Math.PI
... // existing Aquarium class
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
override var water = volume * 0.8
override val shape = "cylinder"
}
- 在
buildAquarium()
建立一個直徑 25 公分、高 45 公分的TowerTank
。列印尺寸。
main.kt:
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium(width = 25, length = 25, height = 40)
myAquarium.printSize()
val myTower = TowerTank(diameter = 25, height = 40)
myTower.printSize()
}
- 執行程式並觀察輸出結果。
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full) aquarium initializing cylinder Width: 25 cm Length: 25 cm Height: 40 cm Volume: 18 l Water: 14.4 l (80.0% full)
有時候,您可能會想為部分相關類別設定要共用的行為或屬性。Kotlin 提供兩種方式:介面和抽象類別。在這項工作中,您會為所有魚類的常見屬性建立抽象 AquariumFish
類別。您建立了名為 FishAction
的介面來定義所有魚的共同行為。
- 抽象類別和介面本身都無法執行個體化,這表示您無法直接建立這些類型的物件。
- 抽象類別含有建構函式。
- 介面不得包含任何建構函式邏輯,也不會儲存任何狀態。
步驟 1:建立抽象類別
- 在 example.myapp 底下,建立新檔案
AquariumFish.kt
。 - 建立一個類別,也稱為
AquariumFish
,並以abstract
標示。 - 新增一個
String
屬性color
,並將其標記為abstract
。
package example.myapp
abstract class AquariumFish {
abstract val color: String
}
- 建立
AquariumFish
、Shark
和Plecostomus
這兩個子類別。 - 由於
color
是抽象層,因此子類別必須導入。讓Shark
變成灰色和Plecostomus
的黃金。
class Shark: AquariumFish() {
override val color = "gray"
}
class Plecostomus: AquariumFish() {
override val color = "gold"
}
- 在 main.kt 中建立一個
makeFish()
函式來測試類別。將Shark
與Plecostomus
執行個體化,然後列印每個項目的顏色。 - 刪除
main()
中的先前測試程式碼,並將呼叫加入makeFish()
。您的程式碼看起來應該會像這樣:
main.kt
:
package example.myapp
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
println("Plecostomus: ${pleco.color}")
}
fun main () {
makeFish()
}
- 執行程式並觀察輸出結果。
⇒ Shark: gray Plecostomus: gold
下圖表示 Shark
類別和 Plecostomus
類別,該類別為抽象類別 AquariumFish
的子類別。
步驟 2:建立介面
- 在 AquariumFish.kt 中,使用
eat()
方法建立名為FishAction
的介面。
interface FishAction {
fun eat()
}
- 為每個子類別加入
FishAction
,然後透過列印eat()
的方式,讓魚類魚類應用程式完成列印工作。
class Shark: AquariumFish(), FishAction {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
class Plecostomus: AquariumFish(), FishAction {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- 使用
makeFish()
函式時,請呼叫eat()
,讓您所建立的每個魚吃什麼。
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
shark.eat()
println("Plecostomus: ${pleco.color}")
pleco.eat()
}
- 執行程式並觀察輸出結果。
⇒ Shark: gray hunt and eat fish Plecostomus: gold eat algae
下圖表示 Shark
類別和 Plecostomus
類別,兩者都包含 FishAction
介面並實作。
使用摘要類別和介面的時機
以上僅列舉幾個簡單的例子,但是如果您有眾多彼此相關的課程,那麼抽象類別和介面有助於維持設計簡潔、有條理且易於維護。
如上所述,抽象類別可以有建構函式,而且介面不能使用介面,但是介面非常類似。請問何時該使用?
使用介面撰寫類別時,該類別的功能會透過包含該類別類別的執行個體進行擴充。與抽象類別相比,組成往往更容易讓程式碼更容易重複使用,以及其原因。此外,一個類別可使用多個介面,但您只能從一個抽象類別中進行子類別。
樂曲通常有助於封裝、降低耦合 (相依程度)、簡潔介面,以及更實用的程式碼。基於以上原因,建議使用介面搭配介面。另一方面,對於某些問題,沿用部分類別的沿用設定通常較為自然。因此,您應該偏好樂曲,但 繼承的 Kotlin 也能讓您這麼做!
- 如果您有許多方法和一或兩個預設導入方式 (例如下方的
AquariumAction
),請採用介面。
interface AquariumAction {
fun eat()
fun jump()
fun clean()
fun catchFish()
fun swim() {
println("swim")
}
}
- 只要是無法完成的課程,您都可以使用抽象類別。
AquariumFish
interface FishAction {
fun eat()
}
abstract class AquariumFish: FishAction {
abstract val color: String
override fun eat() = println("yum")
}
前一項工作介紹了抽象類別、介面和構思概念。「介面委派」是一種先進的技術,可透過輔助 (或委派) 物件實作介面,隨後由類別使用。當您在一個不相關的類別中使用介面時,這項技巧就很實用:您將所需的介面功能加到個別輔助程式類別,而每個類別都使用輔助類別的執行個體來實作功能。
在這項工作中,您將使用介面委派功能新增類別的功能。
步驟 1:建立新介面
- 在 AquariumFish.kt 中,移除
AquariumFish
類別。Plecostomus
和Shark
不會沿用AquariumFish
類別的實作,而是針對魚類動作及其顏色實作介面。 - 建立新介面
FishColor
,將顏色定義為字串。
interface FishColor {
val color: String
}
- 變更
Plecostomus
即可實作兩個介面:FishAction
與FishColor
。您必須覆寫FishColor
中的color
,以及FishAction
中的eat()
。
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- 變更
Shark
類別即可同時實作FishAction
和FishColor
兩種介面,而不是從AquariumFish
繼承。
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
- 完成的程式碼應如下所示:
package example.myapp
interface FishAction {
fun eat()
}
interface FishColor {
val color: String
}
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
步驟 2:製作單調類別
接下來,您需要導入實作 FishColor
的輔助類別,以便設定委派部分的設定。您建立了名為 GoldColor
的基本類別,並實作 FishColor
,就是要指出其顏色是金色。
GoldColor
因此,Kotlin 可讓您宣告使用 object
關鍵字 (而非 class
) 即可只建立一個類別的例項。Kotlin 會建立這個執行個體,該類別會參照類別名稱參照。然後,所有其他物件只能使用這個執行個體 ,因此無法建立此類別的其他執行個體。如果您熟悉單調模式,這就是在 Kotlin 中導入單調的方式。
- 在 AquariumFish.kt 中,為
GoldColor
建立物件。覆寫色彩。
object GoldColor : FishColor {
override val color = "gold"
}
步驟 3:新增 FishColor 的介面委派
現在您已經可以使用介面委派了,
- 在 AquariumFish.kt 中,從
Plecostomus
中移除color
的覆寫值。 - 變更
Plecostomus
類別以取得GoldColor
的顏色。只要在類別宣告中新增by GoldColor
,即可建立委派作業。因此,請使用GoldColor
提供的導入方式,而不是導入FishColor
。因此,每次存取color
時,系統就會委派GoldColor
。
class Plecostomus: FishAction, FishColor by GoldColor {
override fun eat() {
println("eat algae")
}
}
目前我們以 Plecos 的 140 級標示「Plecos」,因此所有魚類都會變為金色,但這些魚實際上的色彩相當多。您可以通過添加一個以 GoldColor
作為 Plecostomus
的默認數字的設置數據。
- 將
Plecostomus
類別變更為使用其建構函式的fishColor
傳送的票證,然後將其設定為GoldColor
。將委派設定從「by GoldColor
」變更為「by fishColor
」。
class Plecostomus(fishColor: FishColor = GoldColor): FishAction,
FishColor by fishColor {
override fun eat() {
println("eat algae")
}
}
步驟 4:新增 FishAction 的介面委派
同理,您可以將介面委派功能用於 FishAction
。
- 在 AquariumFish.kt 中建立一個
PrintingFishAction
類別,實作FishAction
,該類別會列印String
、food
並列印魚類。
class PrintingFishAction(val food: String) : FishAction {
override fun eat() {
println(food)
}
}
- 在
Plecostomus
類別中,移除覆寫函式eat()
,因為您會將其替換為委派設定。 - 在
Plecostomus
的宣告中,將FishAction
委派給PrintingFishAction
,傳遞"eat algae"
。 - 由於所有委派設定皆不含
Plecostomus
類別的內文,因此請移除{}
,因為所有覆寫設定都是由介面委派功能處理
class Plecostomus (fishColor: FishColor = GoldColor):
FishAction by PrintingFishAction("eat algae"),
FishColor by fishColor
下圖表示 Shark
和 Plecostomus
類別,兩者都是由 PrintingFishAction
和 FishColor
介面組成,但會委派實作。
介面委派功能非常強大,而且一般來說,每次使用其他語言的抽象類別時,都應考慮使用這種方法。這個架構可讓您使用樂曲來插入行為,不需要逐一建立不同的子類別。
資料類別與其他語言的 struct
類似,主要用於保存部分資料,但資料類別物件仍為物件。Kotlin 資料類別物件具有一些額外優點,例如列印和複製公用程式。在這項工作中,您會建立一個簡單的資料類別,並瞭解 Kotlin 針對資料類別提供的支援。
步驟 1:建立資料類別
- 在 example.myapp 套件之下新增套件
decor
以存放新程式碼。在 Project 窗格中用滑鼠右鍵按一下 example.myapp,然後選取 File >新增 > 套件 - 在套件中,建立名為「
Decoration
」的新類別。
package example.myapp.decor
class Decoration {
}
- 如要將
Decoration
設為資料類別,請在類別宣告前面加上關鍵字data
。 - 加入名為
rocks
的String
屬性,為全班提供部分資料。
data class Decoration(val rocks: String) {
}
- 將檔案新增至類別外的
makeDecorations()
函式,以使用"granite"
建立及列印Decoration
的執行個體。
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
}
- 新增可呼叫
makeDecorations()
的main()
函式並執行程式。請留意系統建立的合理輸出,因為這是資料類別。
⇒ Decoration(rocks=granite)
- 在
makeDecorations()
中,將另外兩個Decoration
物件執行個體化為「插入畫面」,並列印這些物件。
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
val decoration2 = Decoration("slate")
println(decoration2)
val decoration3 = Decoration("slate")
println(decoration3)
}
- 在
makeDecorations()
中,新增列印聲明,列印出「decoration1
」與「decoration2
」的比較結果,再新增第二個對帳單,比較decoration3
和decoration2
。使用資料類別提供的等於。
println (decoration1.equals(decoration2))
println (decoration3.equals(decoration2))
- 執行程式碼。
⇒ Decoration(rocks=granite) Decoration(rocks=slate) Decoration(rocks=slate) false true
步驟 2:使用解構
為了取得資料物件的屬性並指派給變數,您可以逐一指派一個物件,就像這樣。
val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver
請改為建立變數 (每個資源各一個),並將資料物件指派給變數群組。Kotlin 會將屬性值放在每個變數中。
val (rock, wood, diver) = decoration
這就是所謂的「解構」,有助於使用者快速上手。變數數量應與屬性數量相符,且變數按照在類別中宣告的順序排列。您可以前往 Decoration.kt 測試完整範例。
// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}
fun makeDecorations() {
val d5 = Decoration2("crystal", "wood", "diver")
println(d5)
// Assign all properties to variables.
val (rock, wood, diver) = d5
println(rock)
println(wood)
println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver) crystal wood diver
如果您不需要一個或多個屬性,則可以使用 _
而非變數名稱來略過這些屬性,如下面的程式碼所示。
val (rock, _, diver) = d5
在這項工作中,您會學到一些 Kotlin 的特殊用途類別,包括:
- 單調式課程
- 列舉
- 密閉課程
步驟 1:回顧單調類別
使用 GoldColor
類別回調先前的範例。
object GoldColor : FishColor {
override val color = "gold"
}
由於 GoldColor
的每個例項都具有相同作用,因此會宣告為 object
,而非 class
。只能有一個執行個體。
步驟 2:建立列舉
Kotlin 也支援列舉,可讓您以列舉的方式列舉某個項目並參照名稱,就像其他語言一樣。在陳述式前面加上關鍵字 enum
來宣告列舉。基本列舉宣告只需要名稱清單,但您也可以定義與各個名稱相關的一或多個欄位。
- 在 Decoration.kt 中,請嘗試使用列舉範例。
enum class Color(val rgb: Int) {
RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}
列舉就好比單曲:例如,只能有一個 Color.RED
、一個 Color.GREEN
和一個 Color.BLUE
。在這個範例中,RGB 值會指派給 rgb
屬性來代表顏色元件。您也可以使用 ordinal
屬性取得列舉值,以及使用 name
屬性的名稱。
- 請試試其他列舉範例。
enum class Direction(val degrees: Int) {
NORTH(0), SOUTH(180), EAST(90), WEST(270)
}
fun main() {
println(Direction.EAST.name)
println(Direction.EAST.ordinal)
println(Direction.EAST.degrees)
}
⇒ EAST 2 90
步驟 3:建立密封類別
密封類別是可細分子類別的類別,但只能用於宣告宣告的檔案。如果您嘗試將類別分類為其他檔案,就會收到錯誤訊息。
由於類別和子類別屬於同一個檔案,因此 Kotlin 會以靜態方式知道所有子類別。也就是說,編譯器會檢視所有類別和子類別,並且知道所有類別和子類別都是如此,所以編譯器可以幫您執行額外檢查。
- 在 AquariumFish.kt 中,試試一堂密封的課程,並遵循水上主題。
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()
fun matchSeal(seal: Seal): String {
return when(seal) {
is Walrus -> "walrus"
is SeaLion -> "sea lion"
}
}
「Seal
」類別不能在其他子類別中一併分類。如要新增其他 Seal
類型,您必須在同一個類型中新增這些類型。如此一來,密封類別就能以安全的方式表示固定數量的類型。例如,密封類別適用於從網路 API 傳回成功或錯誤的情況。
這門課程會介紹了許多用途。雖然大部分的內容應熟悉其他物件導向的程式設計語言,不過 Kotlin 還是加入了一些功能,讓程式碼保持簡潔且易於閱讀。
類別和建構函式
- 在 Kotlin 中使用
class
定義類別。 - Kotlin 會自動為屬性建立 setter 和 getter。
- 直接在類別定義中定義主要建構函式。例如:
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
- 如果主要建構函式需要額外的程式碼,請以一或多個
init
區塊的形式編寫。 - 類別可使用
constructor
定義一或多個次要建構函式,但 Kotlin 樣式是使用 Factory 函式。
可見度修飾符和子類別
- 根據預設,Kotlin 中的所有類別和函式皆為
public
,但您可以使用修飾符將顯示設定變更為internal
、private
或protected
。 - 如要建立子類別,父類別必須加上
open
標記。 - 如要覆寫子類別中的方法和屬性,您必須在父類別中以
open
標記方法和屬性。 - 密封類別只能對定義其下的相同檔案進行子類別。在宣告前面加上
sealed
,以建立密封的類別。
資料類別、單調與列舉
- 在陳述式前面加上
data
,建立資料類別。 - 解構是一種能將
data
物件屬性指派給個別變數的簡稱。 - 使用
object
製作單調的課程,不要使用class
。 - 使用
enum class
定義列舉。
抽象類別、介面和委派
- 抽象類別和介面可用於共用兩個類別之間的共同行為。
- 抽象類別可定義屬性和行為,但讓實作程序是子類別。
- 「介面」可定義行為,可為部分或所有行為提供預設導入。
- 使用介面撰寫類別時,該類別的功能會透過包含該類別類別的執行個體進行擴充。
- 介面委派使用組合,但同時將實作的實作委任到介面類別。
- 樂曲功能可以有效使用類別委派功能,為課程新增功能。一般而言,建議採用構件類別,但從抽象類別沿用設定適合某些問題。
Kotlin 說明文件
如果想瞭解本課程的任一主題,或您的困難,建議您選擇 https://kotlinlang.org。
Kotlin 教學課程
https://try.kotlinlang.org 網站包含稱為 Kotlin Koans 的豐富的教學課程、網頁式解譯器,以及包含參考範例的完整參考文件。
Udacity 課程
如要查看這個主題的 Udacity 課程,請參閱程式設計人員 Kotlin 新手上路課程。
IntelliJ IDEA
如需 IntelliJ IDEA 說明文件,請前往 JetBrains 網站。
這個部分會列出在代碼研究室中,受老師主導的課程作業的可能學生作業。由老師自行決定要執行下列動作:
- 視需要指派家庭作業。
- 告知學生如何提交家庭作業。
- 批改家庭作業。
老師可視需要使用這些建議,並視情況指派其他合適的家庭作業。
如果您是自行操作本程式碼研究室,歡迎透過這些家庭作業來測試自己的知識。
回答這些問題
第 1 題
類別採用特殊方法,可以做為建立該類別物件的藍圖。這個方法稱為什麼?
▢ 建構工具
▢ 互動智慧搜尋工具
▢ 建構函式
▢ 藍圖
第 2 題
以下關於介面和抽象類別的敘述何者「不正確」?
▢ 抽象類別可包含建構函式。
▢ 介面沒有建構函式。
▢ 介面和抽象類別可以直接執行個體化。
▢ 抽象屬性必須由抽象類別的子類別實作。
第 3 題
下列何者「不是」Kotlin、能見度等項目的 Kotlin 能見度調整項目?
▢ internal
▢ nosubclass
▢ protected
▢ private
第 4 題
請思考以下資料類別:data class Fish(val name: String, val species:String, val colors:String)
下列何者「不是」用來建立和銷毀 Fish
物件的有效程式碼?
▢ val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")
▢ val (name2, _, colors2) = Fish("Bitey", "shark", "gray")
▢ val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")
▢ val (name4, species4, colors4) = Fish("Harry", "halibut")
第 5 題
假設你擁有的動物園包含許多需要照顧的動物,以下何者「不」包含實作照護服務?
▢ 可食用不同種類的食物的 interface
。
▢ 用來建立不同類型的照護人員的 abstract Caretaker
類別。
▢ 為動物提供乾淨水的 interface
。
▢ 動態饋給項目採用項目的 data
類別。
繼續瀏覽下一門課程:
如需課程簡介,包括其他程式碼研究室的連結,請參閱程式設計人員 Kotlin 新手課程:歡迎參加這堂課程。