1. 事前準備
我們常會在日常生活中為各種情況製作清單,例如待辦事項清單、活動邀請對象清單、願望清單或雜貨清單。在程式設計中,清單也非常實用。舉例來說,應用程式中可能有新聞文章、歌曲、日曆活動或社群媒體貼文等清單。
學習如何建立及使用清單是重要的程式設計概念,可以運用在工具箱中,您可以藉此建立更為複雜的應用程式。
在本程式碼研究室中,您將使用 Kotlin Playground 熟悉 Kotlin 中的清單,並建立程式來訂購不同種類的湯麵。餓了嗎?
必要條件
- 熟悉使用 Kotlin Playground 來建立及編輯 Kotlin 程式。
- 熟悉 Kotlin 課程 Android 基礎知識中第 1 單元的基本 Kotlin 程式設計概念:
main()
函式、函式引數和傳回值、變數、資料類型和作業,以及控制流程陳述式。 - 能夠定義 Kotlin 類別、從中建立物件例項,以及存取其屬性和方法。
- 能夠建立子類別,並瞭解各個子類別彼此繼承的方式。
課程內容
- 如何在 Kotlin 中建立及使用清單
List
和MutableList
的差異,以及兩者的使用時機- 如何疊代清單中的所有項目,並對每個項目執行動作。
建構項目
- 您要在 Kotlin Playground 中嘗試使用清單和清單作業。
- 您要在 Kotlin Playground 中建立使用清單的訂餐程式。
- 您的程式能夠建立訂單、向訂單中加入麵條和蔬菜,然後計算訂單的總費用。
需求條件
- 具備網路連線,可以存取 Kotlin Playground 的電腦。
2. 清單簡介
在先前的程式碼研究室中,您已瞭解 Kotlin 的基本資料類型,例如 Int
、Double
、Boolean
和 String
。這些資料類型可讓您在變數中儲存特定類型的值。但如要儲存多個值,該怎麼辦?這時我們就需要 List
資料類型。
清單是具有特定順序的項目的集合。Kotlin 中有兩種類型的清單:
- 唯讀清單:
List
建立後即無法修改。 - 可變動清單:
MutableList
在建立後可以修改,也就是說,您可以新增、移除或更新其元素。
使用 List
或 MutableList
時,您必須指定它能夠包含的元素類型。例如,List<Int>
包含整數清單,List<String>
則包含字串清單。如果您在程式中定義 Car
類別,則可擁有 List<Car>
,其中包含 Car
物件例項的清單。
瞭解清單的最佳方式就是試用清單。
建立清單
- 開啟 Kotlin Playground,並刪除其中提供的現有程式碼。
- 新增空白的
main()
函式。下列所有程式碼步驟都會位於這個main()
函式中。
fun main() {
}
- 在
main()
中,建立類型為List<Int>
的numbers
變數,因為其中包含整數的唯讀清單。使用 Kotlin 標準程式庫函式listOf()
建立新的List
,然後將清單元素做為以半形逗號隔開的引數傳入。listOf(1, 2, 3, 4, 5, 6)
會傳回介於 1 到 6 之間的整數唯讀清單。
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
- 如果根據指派運算子 (=) 右側的值可推斷 (或推論) 變數類型,則可忽略變數的資料類型。因此,您可以將這行程式碼縮短為以下內容:
val numbers = listOf(1, 2, 3, 4, 5, 6)
- 使用
println()
列印numbers
清單。
println("List: $numbers")
請記住,在字串中加入 $ 表示後面的內容是一個運算式,系統會評估該運算式並將其加入該字串 (請參閱字串範本)。這行程式碼也可寫成 println("List: " + numbers).
- 使用
numbers.size
屬性擷取清單大小,並將其列印出來。
println("Size: ${numbers.size}")
- 執行程式。輸出結果會列出清單的所有元素和清單的大小。請注意,括號
[]
代表這是List
。括號內為numbers
的元素,以半形逗號分隔。另請注意,這些元素的順序與其建立順序相同。
List: [1, 2, 3, 4, 5, 6] Size: 6
存取清單元素
清單的特定功能是,您能夠依元素的索引存取清單的每個元素,索引是代表位置的整數。下圖是我們建立的 numbers
清單圖表,其中顯示了每個元素及其對應的索引。
索引實際上是與第一個元素的偏移值。例如,當您表示 list[2]
時,您並非要求清單的第二個元素,而是要求與第一個元素偏移 2 個位置的元素。因此,list[0]
是第一個元素 (零偏移),list[1]
是第二個元素 (偏移值 1),list[2]
是第三個元素 (偏移值 2),依此類推。
在 main()
函式中現有程式碼的後方加上以下程式碼。請在完成每一步後執行程式碼,以便驗證輸出結果是否正確無誤。
- 列印清單中索引為 0 的第一個元素。您可以呼叫
get()
函式,使得所需索引為numbers.get(0)
,也可以使用簡式語法搭配索引前後的方括號做為numbers[0]
。
println("First element: ${numbers[0]}")
- 接下來,列印清單中索引為 1 的第二個元素。
println("Second element: ${numbers[1]}")
清單的有效索引值 (「索引」) 介於 0 到最後一個索引之間,也就是清單大小減 1。也就是說,您的 numbers
清單中的索引介於 0 至 5 之間。
- 列印清單的最後一個元素,使用
numbers.size - 1
計算其索引,應為5
。存取第 5 個索引處的元素時,系統會傳回6
做為輸出內容。
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
- Kotlin 也支援在清單上執行
first()
和last()
作業。請嘗試呼叫numbers.first()
和numbers.last()
,並查看輸出內容。
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")
您會注意到,numbers.first()
會傳回清單的第一個元素,而 numbers.last()
會傳回清單的最後一個元素。
- 另一個實用的清單作業就是
contains()
方法,可確認清單中是否有指定的元素。舉例來說,如果您有一份公司員工姓名清單,則可以使用contains()
方法確認清單中是否包含指定的姓名。
在 numbers
清單中,呼叫 contains()
方法,並提供清單中的一個整數。numbers.contains(4)
會傳回 true
值。接著,呼叫 contains()
方法,並提供一個不存在於清單中的整數。numbers.contains(7)
會傳回 false
。
println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
- 已完成的程式碼應如下所示。您可選擇留言。
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6)
println("List: $numbers")
println("Size: ${numbers.size}")
// Access elements of the list
println("First element: ${numbers[0]}")
println("Second element: ${numbers[1]}")
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")
// Use the contains() method
println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
}
- 執行程式碼。以下是輸出結果。
List: [1, 2, 3, 4, 5, 6] Size: 6 First element: 1 Second element: 2 Last index: 5 Last element: 6 First: 1 Last: 6 Contains 4? true Contains 7? false
清單為唯讀狀態
- 刪除 Kotlin Playground 中的程式碼,並替換成以下程式碼。
colors
清單已初始化為一份包含 3 個顏色的清單,以Strings
表示。
fun main() {
val colors = listOf("green", "orange", "blue")
}
- 請注意,您無法在唯讀
List
中新增或變更元素。看看如果嘗試將項目加入清單,或嘗試將清單中的元素設定為新的值,藉此修改清單元素,會發生什麼情況。
colors.add("purple")
colors[0] = "yellow"
- 執行程式碼,系統會顯示幾條錯誤訊息。基本上,這些錯誤表示
List
的add()
方法不存在,且您無法變更元素的值。
- 移除不正確的程式碼。
您已經瞭解到,唯讀清單無法變更。不過,有些清單作業並不會變更清單,只會傳回新的清單。其中兩個是 reversed()
和 sorted()
。reversed()
函式會傳回新的清單,其中元素會依相反順序排序;sorted()
會傳回新的清單,元素會以遞增順序排序。
- 新增程式碼可反轉
colors
清單。列印輸出結果。這是一份新清單,其中包含了以相反順序排序的colors
元素。 - 加入第二行程式碼可列印原始
list
,如此您便能看到原始清單並未變更。
println("Reversed list: ${colors.reversed()}")
println("List: $colors")
- 以下是這兩份列印清單的輸出內容。
Reversed list: [blue, orange, green] List: [green, orange, blue]
- 新增程式碼,使用
sorted()
函式傳回List
的已排序版本。
println("Sorted list: ${colors.sorted()}")
輸出內容是一份新的顏色清單,該清單依字母順序排列。太棒了!
Sorted list: [blue, green, orange]
- 您也可以嘗試在未排序的數字清單上使用
sorted()
函式。
val oddNumbers = listOf(5, 3, 7, 1)
println("List: $oddNumbers")
println("Sorted list: ${oddNumbers.sorted()}")
List: [5, 3, 7, 1] Sorted list: [1, 3, 5, 7]
現在,您已瞭解能建立清單有多實用。不過,建立清單後,要是還能修改就更好了,因此,接下來要學的是可變動清單。
3. 可變動清單簡介
可變動清單在建立後可以修改。您可以新增、移除或變更項目。可變動清單提供唯讀清單的全部功能。可變動清單的類型為 MutableList
,您可以透過呼叫 mutableListOf()
來建立。
建立 MutableList
- 刪除
main()
中的現有程式碼。 - 在
main()
函式中,建立一個空白的可變動清單,並指派給名為entrees
的val
變數。
val entrees = mutableListOf()
如果嘗試執行程式碼,就會發生下列錯誤。
Not enough information to infer type variable T
如先前所述,當您建立 MutableList
或 List
時,Kotlin 會嘗試從傳遞的引數中推論清單中包含的元素類型。舉例來說,當您編寫 listOf("noodles")
時,Kotlin 會推論您要建立 String
清單。初始化不含元素的空白清單時,Kotlin 無法推論元素的類型,因此您必須明確指出類型。為此,只要在 mutableListOf
或 listOf
後的角括號中加上類型即可。(在說明文件中,這可能會顯示為 <T>
,其中 T
代表類型參數)。
- 修正變數宣告,指定您要建立
String
類型的可變動清單。
val entrees = mutableListOf<String>()
另一個修正錯誤的方法,就是預先指定變數的資料類型。
val entrees: MutableList<String> = mutableListOf()
- 列印清單。
println("Entrees: $entrees")
- 空白清單的輸出內容會顯示
[]
。
Entrees: []
在清單中新增元素
新增、移除及更新元素時,可變動清單會變得非常有趣。
- 使用
entrees.add("noodles").
將"noodles"
新增至清單。如果成功將元素新增至清單,add()
函式會傳回true
,否則傳回false
。 - 列印清單,確認確實已新增
"noodles"
。
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")
輸出內容如下:
Add noodles: true Entrees: [noodles]
- 將另一個項目
"spaghetti"
新增至清單。
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")
產生的 entrees
清單現在包含兩個項目。
Add spaghetti: true Entrees: [noodles, spaghetti]
與其使用 add()
逐一新增元素,您可以使用 addAll()
一次新增多個元素並傳入清單。
- 建立
moreItems
清單。您不需要變更這個清單,因此請將其設定為val
,不可變動。
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
- 使用
addAll()
,將新清單上的所有項目新增至entrees
。列印產生的清單。
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")
輸出內容顯示新增清單成功。entrees
清單現在共有 5 個項目。
Add list: true Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]
- 現在,請嘗試在這份清單中新增一個數字。
entrees.add(10)
作業失敗,發生錯誤:
The integer literal does not conform to the expected type String
這是因為 entrees
清單需要 String
類型的元素,而您嘗試新增的是 Int
。請記住,僅在清單中新增正確資料類型的元素。否則,您將收到編譯錯誤。Kotlin 能透過這種方式確保您的程式碼在類型安全方面安全無虞。
- 請移除不正確的程式碼行,確保編譯程式碼。
移除清單中的元素
- 呼叫
remove()
即可將"spaghetti"
從清單中移除。再次列印清單。
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
- 移除
"spaghetti"
會傳回 true,因為該元素存在於清單中,可以成功移除。清單現在還剩 4 個項目。
Remove spaghetti: true Entrees: [noodles, ravioli, lasagna, fettuccine]
- 如果您嘗試移除清單中沒有的項目,會發生什麼事?請嘗試使用
entrees.remove("rice")
從清單中移除"rice"
。
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")
remove()
方法會傳回 false
,因為元素不存在,所以無法移除。清單保持不變,還是只有 4 個項目。輸出內容:
Remove item that doesn't exist: false Entrees: [noodles, ravioli, lasagna, fettuccine]
- 您還可以指定要移除的元素索引。使用
removeAt()
移除索引0
處的項目。
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")
removeAt(0)
的傳回值是從清單中移除的第一個元素 ("noodles"
)。entrees
清單現在還剩 3 個項目。
Remove first element: noodles Entrees: [ravioli, lasagna, fettuccine]
- 如要清除整個清單,請呼叫
clear()
。
entrees.clear()
println("Entrees: $entrees")
輸出內容現在會顯示空白清單。
Entrees: []
- 透過 Kotlin,您可使用
isEmpty()
函式,檢查清單是否為空白。請嘗試列印輸出entrees.isEmpty().
println("Empty? ${entrees.isEmpty()}")
輸出結果應為 true,因為清單目前為空白,沒有元素。
Empty? true
如果您想對清單執行作業或想存取特定元素,可以使用 isEmpty()
方法,但建議您先確保清單並非空白。
以下是您為可變動清單撰寫的所有程式碼。您可選擇留言。
fun main() {
val entrees = mutableListOf<String>()
println("Entrees: $entrees")
// Add individual items using add()
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")
// Add a list of items using addAll()
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")
// Remove an item using remove()
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")
// Remove an item using removeAt() with an index
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")
// Clear out the list
entrees.clear()
println("Entrees: $entrees")
// Check if the list is empty
println("Empty? ${entrees.isEmpty()}")
}
4. 迴圈清單
如要對清單中的每個項目執行作業,您可以透過清單執行迴圈 (也就是疊代整個清單)。迴圈功能可與 Lists
和 MutableLists
搭配使用。
迴圈時
其中一種迴圈類型為 while
迴圈。在 Kotlin 中,while
迴圈以 while
關鍵字開頭。迴圈中包含一個程式碼區塊 (位於大括號中),只要括號中的運算式為 true 即可不斷執行。為避免程式碼永久執行 (又稱「無限迴圈」),程式碼區塊必須包含用來變更運算式值的邏輯,這樣一來,運算式最終將產生 false,系統將停止執行迴圈。屆時,系統可結束 while
迴圈,並繼續執行該迴圈之後的程式碼。
while (expression) {
// While the expression is true, execute this code block
}
使用 while
迴圈疊代整個清單。建立變數,以在清單中追蹤您目前查看的 index
。這個 index
變數會保持每次增加 1,直到達到清單的最後一個索引,然後您便可結束迴圈。
- 刪除 Kotlin Playground 中現有的程式碼,得到一個空白的
main()
函式。 - 假設您正在籌辦派對。建立一份清單,其中每個元素都代表每個家庭所回覆的賓客人數。第一個家庭表示自家會有 2 人參加。第二個家庭表示自家會有 4 人參加,依此類推。
val guestsPerFamily = listOf(2, 4, 1, 3)
- 確認賓客總數。撰寫迴圈找到答案。為賓客總數建立
var
,並將其初始化為0
。
var totalGuests = 0
- 初始化
index
變數的var
,如前文所述。
var index = 0
- 撰寫
while
迴圈,以疊代整個清單。條件是只要index
值小於清單的大小,系統就會一直執行程式碼區塊。
while (index < guestsPerFamily.size) {
}
- 在迴圈中,請在目前的
index
處取得清單元素,並將該元素加入賓客變數總數。請注意,totalGuests += guestsPerFamily[index]
和totalGuests = totalGuests + guestsPerFamily[index].
相同
請注意,迴圈的最後一行會使用 index++
將 index
變數遞增 1,這樣下一個迴圈疊代會查看清單中的下一個家庭。
while (index < guestsPerFamily.size) {
totalGuests += guestsPerFamily[index]
index++
}
while
迴圈之後,您可以列印輸出結果。
while ... {
...
}
println("Total Guest Count: $totalGuests")
- 執行程式,輸出內容如下。只需手動把清單中的數字加起來,就能驗證正確答案。
Total Guest Count: 10
以下是完整的程式碼片段:
val guestsPerFamily = listOf(2, 4, 1, 3)
var totalGuests = 0
var index = 0
while (index < guestsPerFamily.size) {
totalGuests += guestsPerFamily[index]
index++
}
println("Total Guest Count: $totalGuests")
使用 while
迴圈時,您必須編寫程式碼來建立變數,以便追蹤索引、取得清單中索引處的元素,以及更新該索引變數。要疊代整個清單,您可以使用一種更快速、精簡的方法。使用 for
迴圈!
For 迴圈
for
迴圈是另一種類型的迴圈。這可讓迴圈清單作業更加輕鬆。這會以 Kotlin 中的 for
關鍵字開頭,在大括號中放置程式碼區塊。用來執行程式碼區塊的條件在括號中表示。
for (number in numberList) {
// For each element in the list, execute this code block
}
在這個範例中,變數 number
設為等於 numberList
的第一個元素,並執行程式碼區塊。接著,number
變數會自動更新為 numberList
的下一個元素,然後再次執行程式碼區塊。對清單的每個元素重複此操作,直到觸及 numberList
的結尾為止。
- 刪除 Kotlin Playground 中的現有程式碼,並替換成以下程式碼:
fun main() {
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
}
- 新增
for
迴圈,以輸出names
清單中的所有項目。
for (name in names) {
println(name)
}
這比直接撰寫為 while
迴圈要簡單許多!
- 輸出內容如下:
Jessica Henry Alicia Jose
清單的一個常見作業就是對每個清單元素嘗試執行某個操作。
- 修改迴圈,使其列印輸出人員姓名的字元數。提示:您可以使用
String
的length
屬性找出String
中的字元數。
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
for (name in names) {
println("$name - Number of characters: ${name.length}")
}
輸出內容:
Jessica - Number of characters: 7 Henry - Number of characters: 5 Alicia - Number of characters: 6 Jose - Number of characters: 4
迴圈中的程式碼並未變更原始 List
。這只會影響列印的內容。
為 1 個清單項目作業編寫指示的方式相當實用,而且每個清單項目都將執行程式碼!使用迴圈功能可避免重複輸入許多相同的程式碼。
現在您已經體驗了建立及使用清單和可變動清單,也對迴圈有所瞭解,接下來在範例使用案例中運用這些知識吧!
5. 靈活運用
在當地餐廳訂餐時,客戶通常會一次訂購多種商品。清單最適合用來儲存訂單相關資訊。此外,您也可運用類別和繼承的知識,來建立更完善、可擴充的 Kotlin 程式,而不是將所有程式碼放在 main()
函式中。
在接下來的一系列工作中,請建立一個 Kotlin 程式,用於訂購不同的食物組合。
首先請查看最後一個程式碼的範例輸出內容。腦力激盪一下,想想您需要建立哪些類別,以協助妥善規劃所有資料?
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15
從輸出內容中,您可發現:
- 有一份訂單清單
- 每筆訂單都有一個編號
- 每筆訂單都可包含麵條和蔬菜等商品的清單
- 每個商品都有價格
- 每筆訂單都有總價,也就是個別商品價格的總和
您可以建立類別來代表 Order
,也可以建立類別來代表每項食品,例如 Noodles
或 Vegetables
。您可能還會發現 Noodles
和 Vegetables
有相似之處,因為這兩者均是食品,而且都有價格。您可以建立 Item
類別,其中包含 Noodle
類別和 Vegetable
類別都可以繼承的共用屬性。這樣一來,您就不用複製 Noodle
類別和 Vegetable
類別中的邏輯。
- 畫面上會顯示下列範例程式碼。專業開發人員經常需要閱讀其他人的程式碼,例如,當他們加入新專案,或投入其他人建立的功能時。閱讀及理解程式碼是一項重要技能。
請花點時間檢查該程式碼,瞭解具體情況。複製這段程式碼並貼到 Kotlin Playground 中執行。在貼上這段新程式碼之前,請務必刪除 Kotlin Playground 中的所有現有程式碼。觀察輸出內容,看看它是否有助於您進一步理解程式碼。
open class Item(val name: String, val price: Int)
class Noodles : Item("Noodles", 10)
class Vegetables : Item("Vegetables", 5)
fun main() {
val noodles = Noodles()
val vegetables = Vegetables()
println(noodles)
println(vegetables)
}
- 您應該會看到類似以下的輸出內容:
Noodles@5451c3a8 Vegetables@76ed5528
以下是程式碼更為詳細的說明。首先是一個名為 Item
的類別,其中建構函式會使用 2 個參數:用於商品的 name
(做為字串) 和 price
(做為整數)。這兩項屬性在傳遞後皆維持不變,因此標示為 val
。由於 Item
是父項類別,子類別由它擴充而來,因此該類別會標上 open
關鍵字。
Noodles
類別建構函式不含任何參數,但會從 Item
擴充,並透過傳遞 "Noodles"
(做為名稱) 與價格 10 呼叫父類別建構函式。Vegetables
類別相似,但會以 "Vegetables"
與價格 5 呼叫父類別建構函式。
main()
函式會初始化 Noodles
和 Vegetables
類別的新物件例項,並將其列印至輸出內容。
覆寫 toString() 方法
當您將物件例項列印至輸出內容時,系統會呼叫物件的 toString()
方法。在 Kotlin 中,每個類別都會自動繼承 toString()
方法。這個方法的預設實作只會傳回物件類型,其中包含例項的記憶體位址。建議您覆寫 toString()
,以便傳回比 Noodles@5451c3a8
和 Vegetables@76ed5528
更有意義且容易使用的結果。
- 在
Noodles
類別中,覆寫toString()
方法,然後使其傳回name
。請注意,Noodles
會繼承父項類別Item
的name
屬性。
class Noodles : Item("Noodles", 10) {
override fun toString(): String {
return name
}
}
- 針對
Vegetables
類別重複相同步驟。
class Vegetables() : Item("Vegetables", 5) {
override fun toString(): String {
return name
}
}
- 執行程式碼。現在,輸出內容看起來更有用:
Noodles
Vegetables
在下一個步驟中,您將變更 Vegetables
類別建構函式來擷取部分參數,並更新 toString()
方法以反映這些額外資訊。
透過訂單自訂蔬菜
為使麵條更有吸引力,您可以在訂單中包含不同的蔬菜。
- 在
main()
函式中,不要初始化不含輸入引數的Vegetables
例項,而是傳遞客戶想要的特定蔬菜。
fun main() {
...
val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
...
}
如果現在嘗試編譯程式碼,系統會顯示以下錯誤訊息:
Too many arguments for public constructor Vegetables() defined in Vegetables
您正在將 3 個字串引數傳遞至 Vegetables
類別建構函式,因此需要修改 Vegetables
類別。
- 更新
Vegetables
類別標頭,以擷取 3 個字串參數,如以下程式碼所示:
class Vegetables(val topping1: String,
val topping2: String,
val topping3: String) : Item ("Vegetables", 5) {
- 現在,您的程式碼會重新編譯。不過,只有客戶想每次都訂購三種蔬菜時,這個解決方法才適用。如果客戶想訂購一種或五種蔬菜,就沒有辦法了。
- 您可以在
Vegetables
類別的建構函式中接受一份蔬菜清單 (長度不限) 以修正此問題,而不用使用每個蔬菜的屬性。List
只能包含Strings
,因此輸入參數的類型為List<String>
。
class Vegetables(val toppings: List<String>) : Item("Vegetables", 5) {
這不是最完美的解決方法,因為在 main()
中,您需要先變更程式碼來建立配料清單,然後才將其傳遞至 Vegetables
建構函式。
Vegetables(listOf("Cabbage", "Sprouts", "Onion"))
還有更好的解決方法。
- 在 Kotlin 中,
vararg
修改程式可讓您將類型相同、數量可變的引數傳遞給函式或建構函式。這樣一來,您就可以提供不同的蔬菜做為單個字串,而非清單。
變更 Vegetables
的類別定義,以採用類型為 String
的 vararg
toppings
。
class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
main()
函式中的程式碼現在正常運作。透過傳遞任何數量的配料字串,即可建立Vegetables
例項。
fun main() {
...
val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
...
}
- 現在,請修改
Vegetables
類別的toString()
方法,使其傳回同樣提及以下配料格式的String
:Vegetables Cabbage, Sprouts, Onion
。
以商品名稱 (Vegetables
) 開頭。接著使用 joinToString()
方法將所有配料加入單一字串。使用 +
運算子將兩個部分加起來,之間留有空格。
class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
override fun toString(): String {
return name + " " + toppings.joinToString()
}
}
- 執行程式,輸出內容應為:
Noodles Vegetables Cabbage, Sprouts, Onion
- 編寫程式時,您需要考量所有可能的輸入內容。如果
Vegetables
建構函式中沒有任何輸入引數,請使用較容易的方式處理toString()
方法。
由於客戶想訂購蔬菜,但並未具體說明要哪些蔬菜,其中一種解決方法就是為他們提供預設由廚師選擇的蔬菜。
如未傳遞任何配料,請更新 toString()
方法以傳回 Vegetables Chef's Choice
。使用您先前學過的 isEmpty()
方法。
override fun toString(): String {
if (toppings.isEmpty()) {
return "$name Chef's Choice"
} else {
return name + " " + toppings.joinToString()
}
}
- 更新
main()
函數,以測試在兩種情況下建立Vegetables
例項的可能性:不含任何建構函式引數以及含有多個引數。
fun main() {
val noodles = Noodles()
val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
val vegetables2 = Vegetables()
println(noodles)
println(vegetables)
println(vegetables2)
}
- 確認輸出內容符合預期。
Noodles Vegetables Cabbage, Sprouts, Onion Vegetables Chef's Choice
建立一個訂單
現在您擁有了一些食物,可以建立訂單了。在程式的 Order
類別中封裝訂單的邏輯。
- 想想看
Order
類別可以使用哪些屬性和方法。如果有所幫助,請再次參考以下的最終程式碼輸出內容範例。
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15 Order #3 Noodles: $10 Vegetables Carrots, Beans, Celery: $5 Total: $15 Order #4 Noodles: $10 Vegetables Cabbage, Onion: $5 Total: $15 Order #5 Noodles: $10 Noodles: $10 Vegetables Spinach: $5 Total: $25
- 您可能已經想到以下幾點:
訂單類別
屬性:訂單號碼、商品清單
方法:新增商品、新增多個商品、列印訂單摘要 (含價格)
- 首先關注屬性,每個屬性的資料類型應該是什麼?它們對於類別應為公開還是私人?它們應該做為引數傳遞還是在類別中定義?
- 您可以透過多種方式來實作,以下是一個解決方法。建立含有整數
orderNumber
建構函式參數的class
Order
。
class Order(val orderNumber: Int)
- 您可能無法預先知道訂單中的所有商品,因此無需將商品清單作為引數傳遞。這可以宣告為頂層類別變數,並初始化為空白的
MutableList
,用於儲存Item
類型的元素。標示變數private
,讓系統只允許這個類別直接修改商品清單。這種做法可以防止此類別以外的程式碼意外修改清單。
class Order(val orderNumber: Int) {
private val itemList = mutableListOf<Item>()
}
- 現在,請將方法加入類別定義中。您可以隨意為每種方法挑選合理的名稱,目前每個方法內的實作邏輯則可以留空。此外,還要決定需要哪些函式引數和傳回值。
class Order(val orderNumber: Int) {
private val itemList = mutableListOf<Item>()
fun addItem(newItem: Item) {
}
fun addAll(newItems: List<Item>) {
}
fun print() {
}
}
addItem()
方法似乎最直接,因此先實作該函式。該函式會擷取新的Item
,而該方法應將其新增至itemList
。
fun addItem(newItem: Item) {
itemList.add(newItem)
}
- 接下來請實作
addAll()
方法。該方法會擷取唯讀商品清單。將所有商品加入內部商品清單。
fun addAll(newItems: List<Item>) {
itemList.addAll(newItems)
}
- 接著,請實作
print()
方法,藉此將所有商品及其價格的摘要以及訂單的總價輸出至輸出內容。
請先列印輸出訂單號碼。接下來,使用迴圈疊代訂單清單中的所有商品。請輸出每個商品及其對應價格。同時,保留到目前為止的總價,並在疊代整個清單時繼續增加總價。最後,輸出總價。嘗試自行實作這個邏輯。如需相關協助,請查看下列解決方法。
建議您加入貨幣符號,讓輸出內容更容易閱讀。以下是實作該解決方法的一個方式。此程式碼使用 $ 貨幣符號,但也可以視需要換算成當地幣別符號。
fun print() {
println("Order #${orderNumber}")
var total = 0
for (item in itemList) {
println("${item}: $${item.price}")
total += item.price
}
println("Total: $${total}")
}
針對 itemList
中的每個 item
,請輸出 item
(這將觸發要在 item
上呼叫的 toString()
),然後是商品的 price
。同樣,在執行迴圈之前,請將 total
整數變數初始化為 0。接著,在 total
中加入目前商品的價格,繼續增加總價。
建立多個訂單
- 在
main()
函式中建立Order
例項,藉此測試程式碼。請先刪除main()
函式中現有的內容。 - 您可以使用這些訂單範例,也可以自行建立訂單。嘗試使用訂單中各種不同的商品組合,確保測試了程式碼中的所有程式碼路徑。例如,在
Order
類別中測試addItem()
和addAll()
方法,建立不含引數與含有引數的Vegetables
例項,依此類推。
fun main() {
val order1 = Order(1)
order1.addItem(Noodles())
order1.print()
println()
val order2 = Order(2)
order2.addItem(Noodles())
order2.addItem(Vegetables())
order2.print()
println()
val order3 = Order(3)
val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
order3.addAll(items)
order3.print()
}
- 上述程式碼的輸出內容應如下所示。確認總價已正確相加。
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15 Order #3 Noodles: $10 Vegetables Carrots, Beans, Celery: $5 Total: $15
做得好!現在看起來像是點餐了!
6. 改善程式碼
保留訂單清單
如果您建構能實際在麵店中使用的程式,請務必追蹤所有客戶訂單的清單。
- 建立清單以儲存所有訂單。它是唯讀清單還是可變動清單嗎?
- 將這段程式碼新增至
main()
函式。首先,請將清單初始化為空白。接著,每次建立訂單時,將訂單加入清單中。
fun main() {
val ordersList = mutableListOf<Order>()
val order1 = Order(1)
order1.addItem(Noodles())
ordersList.add(order1)
val order2 = Order(2)
order2.addItem(Noodles())
order2.addItem(Vegetables())
ordersList.add(order2)
val order3 = Order(3)
val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
order3.addAll(items)
ordersList.add(order3)
}
由於訂單會隨著時間增加,因此清單應為 Order
類型的 MutableList
。然後使用 MutableList
上的 add()
方法新增每個訂單。
- 建立訂單清單後,您可以使用迴圈來列印每個訂單。在訂單之間列印空白行,讓輸出內容更容易閱讀。
fun main() {
val ordersList = mutableListOf<Order>()
...
for (order in ordersList) {
order.print()
println()
}
}
這麼做會移除 main()
函式中的重複程式碼,讓程式碼更容易閱讀!輸出內容應與先前相同。
實作訂單的建構工具模式
如果要讓 Kotlin 程式碼更簡潔,您可以使用建構工具模式來建立訂單。建構工具模式是程式設計中的設計模式,可逐步指導您建構複雜的物件。
- 請傳回已變更的
Order
,不要傳回Order
類別中addItem()
和addAll()
方法的Unit
(或不傳回任何內容)。Kotlin 提供關鍵字this
以參照目前的物件例項。在addItem()
和addAll()
方法中,您會透過傳回this
來傳回目前的Order
。
fun addItem(newItem: Item): Order {
itemList.add(newItem)
return this
}
fun addAll(newItems: List<Item>): Order {
itemList.addAll(newItems)
return this
}
- 現在,在
main()
函式中,您可以將呼叫鏈結在一起,如以下程式碼所示。這個程式碼會建立新的Order
,並充分運用建構工具模式。
val order4 = Order(4).addItem(Noodles()).addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)
Order(4)
會傳回 Order
例項,您之後可以在上面呼叫 addItem(Noodles())
。addItem()
方法會傳回相同的 Order
例項 (採用新的狀態),您可以用再次在上面呼叫 addItem()
的方式處理蔬菜。傳回的 Order
結果可以儲存在 order4
變數中。
用來建立 Orders
的現有程式碼仍可使用,因此可保持不變。雖然並非一定要鏈結這些呼叫,但這是一種常見且推薦的做法,可讓您善用函式的傳回值。
- 此時您甚至不需要將訂單儲存在變數中。在
main()
函式中 (在列印輸出訂單的最終迴圈之前),直接建立Order
,並將其新增至orderList
。如果每種方法呼叫都自成一行,程式碼也更容易讀取。
ordersList.add(
Order(5)
.addItem(Noodles())
.addItem(Noodles())
.addItem(Vegetables("Spinach")))
- 執行程式碼,以下是預期的輸出內容:
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15 Order #3 Noodles: $10 Vegetables Carrots, Beans, Celery: $5 Total: $15 Order #4 Noodles: $10 Vegetables Cabbage, Onion: $5 Total: $15 Order #5 Noodles: $10 Noodles: $10 Vegetables Spinach: $5 Total: $25
恭喜您完成本程式碼研究室!
現在,您已經瞭解了在清單中儲存資料、變動清單以及透過清單執行迴圈有多實用。在下一個程式碼研究室中,在 Android 應用程式中運用這些知識,以在畫面上顯示資料清單!
7. 解決方案程式碼
以下是 Item
、Noodles
、Vegetables
和 Order
類別的解決方案程式碼。此外,main()
函式也會顯示這些類別的使用方式。實作這項程式的方法有很多,因此您的程式碼可能會略有不同。
open class Item(val name: String, val price: Int)
class Noodles : Item("Noodles", 10) {
override fun toString(): String {
return name
}
}
class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
override fun toString(): String {
if (toppings.isEmpty()) {
return "$name Chef's Choice"
} else {
return name + " " + toppings.joinToString()
}
}
}
class Order(val orderNumber: Int) {
private val itemList = mutableListOf<Item>()
fun addItem(newItem: Item): Order {
itemList.add(newItem)
return this
}
fun addAll(newItems: List<Item>): Order {
itemList.addAll(newItems)
return this
}
fun print() {
println("Order #${orderNumber}")
var total = 0
for (item in itemList) {
println("${item}: $${item.price}")
total += item.price
}
println("Total: $${total}")
}
}
fun main() {
val ordersList = mutableListOf<Order>()
// Add an item to an order
val order1 = Order(1)
order1.addItem(Noodles())
ordersList.add(order1)
// Add multiple items individually
val order2 = Order(2)
order2.addItem(Noodles())
order2.addItem(Vegetables())
ordersList.add(order2)
// Add a list of items at one time
val order3 = Order(3)
val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
order3.addAll(items)
ordersList.add(order3)
// Use builder pattern
val order4 = Order(4)
.addItem(Noodles())
.addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)
// Create and add order directly
ordersList.add(
Order(5)
.addItem(Noodles())
.addItem(Noodles())
.addItem(Vegetables("Spinach"))
)
// Print out each order
for (order in ordersList) {
order.print()
println()
}
}
8. 摘要
Kotlin 提供的功能可協助您透過 Kotlin 標準資料庫輕鬆管理和操控資料集合。集合可定義為具有相同資料類型的多個物件。Kotlin 提供了不同的基本集合類型:清單、組合和對應。本程式碼研究室主要著重於清單,我們會在今後的程式碼研究室中詳細介紹組合和對應。
- 清單是特定類型元素的有序集合,例如
Strings.
清單 - 索引是反映元素位置的整數位置 (例如
myList[2]
)。 - 在清單中,第一個元素位於索引 0 處 (例如
myList[0]
),最後一個元素則位於myList.size-1
處 (例如myList[myList.size-1]
或myList.last()
)。 - 清單分為兩種類型:
List
和MutableList.
List
處於唯讀狀態,在初始化後無法修改。不過,您可以執行sorted()
和reversed()
等作業,這些作業可在不變更原始清單的情況下傳回新清單。MutableList
建立後可以修改,例如新增、移除或修改元素。- 您可以使用
addAll()
將商品清單新增至可變動清單。 - 使用
while
迴圈執行程式碼區塊,直到運算式評估為 false 並結束迴圈為止。
while (expression) {
// While the expression is true, execute this code block
}
- 使用
for
迴圈疊代清單的所有商品:
for (item in myList) {
// Execute this code block for each element of the list
}
vararg
修飾符可讓您將數量可變的引數傳遞給函式或建構函式。