了解如何在在 Kotlin 中創建 List 並循環使用該 List。
學習目標
如何在 Kotlin 中建立及使用 List
List
和 MutableList
的差異,以及兩者的使用時機
如何迭代 List
中的所有項目,並對每個項目執行動作。
List 簡介 在先前的程式碼研究室中,您已瞭解 Kotlin 的基本資料類型,例如 Int
、Double
、Boolean
和 String
。這些資料類型可讓您在變數中儲存特定類型的值。但如要儲存多個值,該怎麼辦?這時我們就需要 List 資料類型。
清單是具有特定順序的項目的集合。Kotlin 中有兩種類型的清單:
唯讀列表:List
建立後即無法修改。
可變動列表:MutableList
在建立後可以修改,也就是說,您可以新增、移除或更新其元素。
使用 List
或 MutableList
時,您必須指定它能夠包含的元素類型。例如,List<Int>
包含整數清單,List<String>
則包含字串清單。如果您在程式中定義 Car
類別,則可擁有 List<Car>
,其中包含 Car
物件例項的清單。
建立 List
開啟 Kotlin Playground ,並刪除其中提供的現有程式碼。
新增空白的 main()
函式。下列所有程式碼步驟都會位於這個 main()
函式中。
在 main()
中,建立類型為 List<Int>
的 numbers
變數,因為其中包含整數的唯讀清單。使用 Kotlin 標準程式庫函式 listOf()
建立新的 List
,然後將清單元素做為以半形逗號隔開的引數傳入。listOf(1, 2, 3, 4, 5, 6)
會傳回介於 1 到 6 之間的整數唯讀清單。
1 val numbers: List<Int > = listOf(1 , 2 , 3 , 4 , 5 , 6 )
如果根據指派運算子 (=
) 右側的值可推斷 (或推論) 變數類型,則可忽略變數的資料類型。因此,您可以將這行程式碼縮短為以下內容:
1 val numbers = listOf(1 , 2 , 3 , 4 , 5 , 6 )
使用 println()
列印 numbers
列表。
1 println("List: $numbers " )
請記住,在字串中加入 $
表示後面的內容是一個運算式 ,系統會評估該運算式並將其加入該字串 (請參閱字串範本)。這行程式碼也可寫成 println("List: " + numbers)
.
使用 numbers.size
屬性擷取 List 大小,並將其列印出來。
1 println("Size: ${numbers.size} " )
執行程式。輸出結果會列出清單的所有元素和清單的大小。請注意,括號 []
代表這是 List
。括號內為 numbers
的元素,以半形逗號分隔。另請注意,這些元素的順序與其建立順序相同。
1 2 List: [1, 2, 3, 4, 5, 6] Size: 6
存取 List 元素 List
的特定功能是,能夠依 元素(Element)
的 索引(Index)
存取 List
的每個 元素(Element)
,索引(Index)
是代表位置的整數。下圖是我們建立的 numbers List圖表,其中顯示了每個元素及其對應的索引。
索引實際上是與第一個元素的偏移值。例如,當您表示 list[2]
時,您並非要求清單的第二個元素,而是要求與第一個元素偏移 2 個位置的元素。因此,list[0]
是第一個元素 (零偏移),list[1]
是第二個元素 (偏移值 1),list[2]
是第三個元素 (偏移值 2),依此類推。
列印 List
中索引為 0
的第一個元素。您可以呼叫 get()
函式,使得所需索引為 numbers.get(0)
,也可以使用簡式語法搭配索引前後的方括號做為 numbers[0]
。
1 2 println("First element: ${numbers.get(0 )} " ) println("First element: ${numbers[0 ]} " )
接下來,列印 List
中索引為 1
的第二個元素。
1 2 println("Second element: ${numbers.get(1 )} " ) println("Second element: ${numbers[1 ]} " )
List
的有效索引值 (「索引」) 介於 0 到最後一個索引之間,也就是 List
大小減 1。也就是說,您的 numbers
List
中的索引介於 0 至 5 之間。
列印 List
的最後一個元素,使用 numbers.size - 1
計算其索引,應為 5。存取第 5 個索引處的元素時,系統會傳回 6 做為輸出內容。
1 2 println("Last index: ${numbers.size - 1 } " ) println("Last element: ${numbers[numbers.size - 1 ]} " )
Kotlin 也支援在 List
上執行 first()
和 last()
作業。請嘗試呼叫 numbers.first()
和 numbers.last()
,並查看輸出內容。
1 2 println("First: ${numbers.first()} " ) println("Last: ${numbers.last()} " )
numbers.first()
會傳回 List 的第一個元素,而 numbers.last()
會傳回 List 的最後一個元素。
另一個實用的 List
作業就是 contains()
方法,可確認 List
中是否有指定的元素。舉例來說,如果您有一份公司員工姓名清單,則可以使用 contains()
方法確認 List
中是否包含指定的姓名。
在 numbers
List 中,呼叫 contains()
方法,並提供 List 中的一個整數。numbers.contains(4)
會傳回 true
值。接著,呼叫 contains()
方法,並提供一個不存在於 List 中的整數。numbers.contains(7)
會傳回 false
。
1 2 println("Contains 4? ${numbers.contains(4 )} " ) println("Contains 7? ${numbers.contains(7 )} " )
已完成的程式碼應如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fun main () { val numbers = listOf(1 , 2 , 3 , 4 , 5 , 6 ) println("List: $numbers " ) println("Size: ${numbers.size} " ) 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()} " ) println("Contains 4? ${numbers.contains(4 )} " ) println("Contains 7? ${numbers.contains(7 )} " ) }
執行程式碼。以下是輸出結果。
1 2 3 4 5 6 7 8 9 10 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
List 已初始化為一份包含 3 個顏色的清單,以 Strings
表示。
1 2 3 fun main () { val colors = listOf("green" , "orange" , "blue" ) }
請注意,您無法在唯讀 List
中新增或變更元素。看看如果嘗試將項目加入 List,或嘗試將 List 中的元素設定為新的值,藉此修改 List 元素,會發生什麼情況。
1 2 colors.add("purple" ) colors[0 ] = "yellow"
執行程式碼,系統會顯示幾條錯誤訊息。基本上,這些錯誤表示 List
的 add()
方法不存在,且您無法變更元素的值。
移除不正確的程式碼。
您已經瞭解到,唯讀 List 無法變更。不過,有些 List 作業並不會變更 List,只會傳回新的 List。其中兩個是 reversed()
和 sorted()
。reversed()
函式會傳回新的 List,其中元素會依相反順序 排序;sorted()
會傳回新的 List,元素會以遞增順序 排序。
新增程式碼可反轉 colors
List。列印輸出結果。這是一份新 List,其中包含了以相反順序排序的 colors 元素。
加入第二行程式碼可列印原始 List
,如此您便能看到原始 List 並未變更。
1 2 println("Reversed list: ${colors.reversed()} " ) println("List: $colors " )
執行結果:
1 2 Reversed list: [blue, orange, green] List: [green, orange, blue]
新增程式碼,使用 sorted()
函式傳回 List 的已排序版本。
1 println("Sorted list: ${colors.sorted()} " )
執行結果:
1 Sorted list: [blue, green, orange]
輸出內容是一份新的顏色 List,該 List 依字母順序 排列。
也可以嘗試在未排序的數字清單 上使用 sorted()
函式。
1 2 3 val oddNumbers = listOf(5 , 3 , 7 , 1 )println("List: $oddNumbers " ) println("Sorted list: ${oddNumbers.sorted()} " )
執行結果:
1 2 List: [5, 3, 7, 1] Sorted list: [1, 3, 5, 7]
可變動 List 簡介 可變動 List 在建立後可以修改。您可以新增、移除或變更項目。可變動 List 提供唯讀 List 的全部功能。可變動 List 的類型為 MutableList
,您可以透過呼叫 mutableListOf()
來建立。
建立 MutableList
刪除 main()
中的現有程式碼。
在 main()
函式中,建立一個空白的可變動 List,並指派給名為 entrees
的 val
變數。
1 val entrees = mutableListOf()
如果嘗試執行程式碼,就會發生下列錯誤。
1 Not enough information to infer type variable T
注意: 當建立 MutableList 或 List 時,Kotlin 會嘗試從傳遞的引數中推論清單中包含的元素類型 。舉例來說,當您編寫 listOf(“noodles”) 時,Kotlin 會推論您要建立 String 清單。初始化不含元素的空白清單 時,Kotlin 無法推論元素的類型 ,因此您必須明確指出類型。為此,只要在 mutableListOf 或 listOf 後的角括號中加上類型即可。(在說明文件中,這可能會顯示為 <T>
,其中 T 代表類型參數 )。
修正變數宣告,指定您要建立 String
類型的可變動 List。
1 val entrees = mutableListOf<String>()
另一個修正錯誤的方法,就是預先指定變數的資料類型。
1 val entrees: MutableList<String> = mutableListOf()
val
可用於可變動 List,因為 entrees
變數包含對 List 的參照;即使 List 內容有所變更,參照也不會改變。
列印 List。
1 println("Entrees: $entrees " )
執行結果:
在清單中新增元素 新增、移除及更新元素時,可變動清單會變得非常有趣。
使用 entrees.add("noodles")
. 將 “noodles” 新增至 List。如果成功將元素新增至 List,add()
函式會傳回 true
,否則傳回 false
。
列印 List,確認確實已新增 “noodles”。
1 2 println("Add noodles: ${entrees.add("noodles" )} " ) println("Entrees: $entrees " )
輸出內容如下:
1 2 Add noodles: true Entrees: [noodles]
將另一個項目 “spaghetti” 新增至 List。
1 2 println("Add spaghetti: ${entrees.add("spaghetti" )} " ) println("Entrees: $entrees " )
產生的 entrees List 現在包含兩個項目。
1 2 Add spaghetti: true Entrees: [noodles, spaghetti]
與其使用 add()
逐一新增元素,您可以使用 addAll()
一次新增多個元素並傳入 List。
建立 moreItems
List。您不需要變更這個 List,因此請將其設定為 val
,不可變動。
1 val moreItems = listOf("ravioli" , "lasagna" , "fettuccine" )
使用 addAll()
,將新 List 上的所有項目新增至 entrees
。列印產生的 List。
1 2 println("Add list: ${entrees.addAll(moreItems)} " ) println("Entrees: $entrees " )
輸出內容顯示新增 List 成功。entrees List 現在共有 5 個項目。
1 2 Add list: true Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]
現在,請嘗試在這份 List 中新增一個數字。
作業失敗,發生錯誤:
1 The integer literal does not conform to the expected type String
這是因為 entrees
List 需要 String
類型的元素,而您嘗試新增的是 Int
。請記住,僅在 List 中新增正確資料類型的元素。否則,您將收到編譯錯誤。
請移除不正確的程式碼行,確保編譯程式碼。
移除 List 中的元素
呼叫 remove()
即可將 “spaghetti” 從 List 中移除。再次列印 List。
1 2 println("Remove spaghetti: ${entrees.remove("spaghetti" )} " ) println("Entrees: $entrees " )
移除 “spaghetti” 會傳回 true
,因為該元素存在於 List 中,可以成功移除。List 現在還剩 4 個項目。
1 2 Remove spaghetti: true Entrees: [noodles, ravioli, lasagna, fettuccine]
如果您嘗試移除 List 中沒有的項目,會發生什麼事?請嘗試使用 entrees.remove("rice")
從 List 中移除 “rice”。
1 2 println("Remove item that doesn't exist: ${entrees.remove("rice" )} " ) println("Entrees: $entrees " )
remove()
方法會傳回 false
,因為元素不存在,所以無法移除。List 保持不變,還是只有 4 個項目。
1 2 Remove item that doesn't exist: false Entrees: [noodles, ravioli, lasagna, fettuccine]
您還可以指定要移除的元素索引。使用 removeAt()
移除索引 0
處的項目。
1 2 println("Remove first element: ${entrees.removeAt(0 )} " ) println("Entrees: $entrees " )
removeAt(0)
的傳回值是從 List 中移除的第一個元素 (“noodles”)。entrees
List 現在還剩 3 個項目。
1 2 Remove first element: noodles Entrees: [ravioli, lasagna, fettuccine]
如要清除整個 List,請呼叫 clear()
。
1 2 entrees.clear() println("Entrees: $entrees " )
輸出內容現在會顯示空白 List。
透過 Kotlin,您可使用 isEmpty()
函式,檢查 List 是否為空白。請嘗試列印輸出 entrees.isEmpty()
。
1 println("Empty? ${entrees.isEmpty()} " )
輸出結果應為 true
,因為 List 目前為空白,沒有元素。
以下是以上可變動 List 撰寫的所有程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 fun main () { val entrees = mutableListOf<String>() println("Entrees: $entrees " ) println("Add noodles: ${entrees.add("noodles" )} " ) println("Entrees: $entrees " ) println("Add spaghetti: ${entrees.add("spaghetti" )} " ) println("Entrees: $entrees " ) val moreItems = listOf("ravioli" , "lasagna" , "fettuccine" ) println("Add list: ${entrees.addAll(moreItems)} " ) println("Entrees: $entrees " ) println("Remove spaghetti: ${entrees.remove("spaghetti" )} " ) println("Entrees: $entrees " ) println("Remove item that doesn't exist: ${entrees.remove("rice" )} " ) println("Entrees: $entrees " ) println("Remove first element: ${entrees.removeAt(0 )} " ) println("Entrees: $entrees " ) entrees.clear() println("Entrees: $entrees " ) println("Empty? ${entrees.isEmpty()} " ) }
迴圈 List 如要對 List 中的每個項目執行作業,您可以透過 List 執行迴圈 (也就是迭代整個 List)。迴圈功能可與 Lists
和 MutableLists
搭配使用。
While 迴圈 其中一種迴圈類型為 while
迴圈。在 Kotlin 中,while
迴圈以 while
關鍵字開頭。迴圈中包含一個程式碼區塊 (位於大括號中),只要括號中的運算式為 true
即可不斷執行。為避免程式碼永久執行 (又稱「無限迴圈」),程式碼區塊必須包含用來變更運算式值的邏輯,這樣一來,運算式最終將產生 false
,系統將停止執行迴圈。屆時,系統可結束 while
迴圈,並繼續執行該迴圈之後的程式碼。
1 2 3 while (expression) { }
注意: while 迴圈不必包含 List (這裡 的範例),但適合 List 使用。
使用 while
迴圈疊代整個 List。建立變數,以在 List 中追蹤您目前查看的 index。這個 index 變數會保持每次增加 1,直到達到 List 的最後一個索引,然後您便可結束迴圈。
刪除 Kotlin Playground 中現有的程式碼,得到一個空白的 main()
函式。
假設您正在籌辦派對。建立一份 List,其中每個元素都代表每個家庭所回覆的賓客人數。第一個家庭表示自家會有 2 人參加。第二個家庭表示自家會有 4 人參加,依此類推。
1 val guestsPerFamily = listOf(2 , 4 , 1 , 3 )
確認賓客總數。撰寫迴圈找到答案。為賓客總數建立 var
,並將其初始化為 0
。
初始化 index
變數的 var
,如前文所述。
撰寫 while
迴圈,以迭代整個清單。條件是只要 index
值小於 List 的大小,系統就會一直執行程式碼區塊。
1 2 3 while (index < guestsPerFamily.size) {}
在迴圈中,請在目前的 index
處取得 List 元素,並將該元素加入賓客變數總數。請注意,totalGuests += guestsPerFamily[index]
和 totalGuests = totalGuests + guestsPerFamily[index]
相同。
迴圈的最後一行會使用 index++
將 index
變數遞增 1,這樣下一個迴圈迭代會查看 List 中的下一個家庭。
1 2 3 4 while (index < guestsPerFamily.size) { totalGuests += guestsPerFamily[index] index++ }
我的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 fun main () { val guestsPerFamily = listOf(2 , 4 , 1 , 3 ) var totalGuests = 0 var index = 0 while (index < guestsPerFamily.size) { println("index: $index " ) totalGuests += guestsPerFamily[index] println("totalGuests: $totalGuests " ) index ++ } }
執行結果:
1 2 3 4 5 6 7 8 index: 0 totalGuests: 2 index: 1 totalGuests: 6 index: 2 totalGuests: 7 index: 3 totalGuests: 10
根據以上結果,可以看出 index
加 1 時,totalGuest
也隨之增加。
您可以在 while
迴圈之後列印輸出結果。
1 2 3 4 while ... { ... } println("Total Guest Count: $totalGuests " )
執行程式,輸出內容如下。只需手動把 List 中的數字加起來,就能驗證正確答案。
以下是完整的程式碼片段:
1 2 3 4 5 6 7 8 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 " )
For 迴圈 for
迴圈是另一種類型的迴圈。這可讓迴圈 List 作業更加輕鬆。這會以 Kotlin 中的 for
關鍵字開頭,在大括號中放置程式碼區塊。用來執行程式碼區塊的條件在括號中表示。
1 2 3 for (number in numberList) { }
在這個範例中,變數 number
設為等於 numberList
的第一個元素,並執行程式碼區塊。接著,number
變數會自動更新為 numberList
的下一個元素,然後再次執行程式碼區塊。對清單的每個元素重複此操作,直到觸及 numberList
的結尾為止。
刪除 Kotlin Playground 中的現有程式碼,並替換成以下程式碼:
1 2 3 fun main () { val names = listOf("Jessica" , "Henry" , "Alicia" , "Jose" ) }
新增 for
迴圈,以輸出 names
List 中的所有項目。
1 2 3 for (name in names) { println(name) }
這比直接撰寫為 while
迴圈要簡單許多!
輸出內容如下:
1 2 3 4 Jessica Henry Alicia Jose
List 的一個常見作業就是對每個 List 元素嘗試執行某個操作。
修改迴圈,使其列印輸出人員姓名的字元數。提示:您可以使用 String
的 length 屬性找出 String
中的字元數。
1 2 3 4 val names = listOf("Jessica" , "Henry" , "Alicia" , "Jose" )for (name in names) { println("$name - Number of characters: ${name.length} " ) }
輸出內容:
1 2 3 4 Jessica - Number of characters: 7 Henry - Number of characters: 5 Alicia - Number of characters: 6 Jose - Number of characters: 4
注意: 以下是您可以使用 for 迴圈執行的其他一些變體,包括將它們用於具有特定步驟的範圍 (而不是每次遞增 1)。for (item in list) print(item) // Iterate over items in a list for (item in 'b'..'g') print(item) // Range of characters in an alphabet for (item in 1..5) print(item) // Range of numbers for (item in 5 downTo 1) print(item) // Going backward for (item in 3..6 step 2) print(item) // Prints: 35
您可以在本程式碼研究室結尾的說明文件中找到更多資訊。
靈活運用 在當地餐廳訂餐時,客戶通常會一次訂購多種商品。List 最適合用來儲存訂單相關資訊。此外,您也可運用類別和繼承的知識,來建立更完善、可擴充的 Kotlin 程式,而不是將所有程式碼放在 main() 函式中。
在接下來的一系列工作中,請建立一個 Kotlin 程式,用於訂購不同的食物組合。
首先請查看最後一個程式碼的範例輸出內容。腦力激盪一下,想想您需要建立哪些類別,以協助妥善規劃所有資料?
1 2 3 4 5 6 7 8 Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15
從輸出內容中,您可發現:
有一份訂單列表
每筆訂單都有一個編號
每筆訂單都可包含 Noodles(麵條)
和 Vegetables(蔬菜)
等商品的列表
每個商品都有價格
每筆訂單都有 Total(總價)
,也就是個別商品價格的總和
您可以建立類別來代表 Order
,也可以建立類別來代表每項食品,例如 Noodles
或 Vegetables
。您可能還會發現 Noodles
和 Vegetables
有相似之處,因為這兩者均是食品,而且都有價格。您可以建立 Item
類別,其中包含 Noodle
類別和 Vegetable
類別都可以繼承的共用屬性。這樣一來,您就不用複製 Noodle
類別和 Vegetable
類別中的邏輯。
畫面上會顯示下列範例程式碼。專業開發人員經常需要閱讀其他人的程式碼,例如,當他們加入新專案,或投入其他人建立的功能時。閱讀及理解程式碼是一項重要技能。
請花點時間檢查該程式碼,瞭解具體情況。複製這段程式碼並貼到 Kotlin Playground 中執行。在貼上這段新程式碼之前,請務必刪除 Kotlin Playground 中的所有現有程式碼。觀察輸出內容,看看它是否有助於您進一步理解程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 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) }
您應該會看到類似以下的輸出內容:
1 2 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
屬性。
1 2 3 4 5 class Noodles : Item ("Noodles" , 10 ) { override fun toString () : String { return name } }
針對 Vegetables
類別重複相同步驟。
1 2 3 4 5 class Vegetables () : Item("Vegetables" , 5 ) { override fun toString () : String { return name } }
執行程式碼。現在,輸出內容看起來更有用:
透過訂單自訂蔬菜 為使麵條更有吸引力,您可以在訂單中包含不同的蔬菜。
在 main()
函式中,不要初始化不含輸入引數的 Vegetables
例項,而是傳遞客戶想要的特定蔬菜。
1 2 3 4 5 fun main () { ... val vegetables = Vegetables("Cabbage" , "Sprouts" , "Onion" ) ... }
如果現在嘗試編譯程式碼,系統會顯示以下錯誤訊息:
1 Too many arguments for public constructor Vegetables() defined in Vegetables
您正在將 3 個字串引數傳遞至 Vegetables
類別建構函式,因此需要修改 Vegetables
類別。
更新 Vegetables
類別標頭,以擷取 3 個字串參數,如以下程式碼所示:
1 2 3 class Vegetables (val topping1: String, val topping2: String, val topping3: String) : Item ("Vegetables" , 5 ) {
現在,您的程式碼會重新編譯。不過,只有客戶想每次都訂購三種蔬菜時,這個解決方法才適用。如果客戶想訂購一種或五種蔬菜,就沒有辦法了。
您可以在 Vegetables
類別的建構函式中接受一份蔬菜清單 (長度不限) 以修正此問題,而不用使用每個蔬菜的屬性。List
只能包含 Strings
,因此輸入參數的類型為 List<String>
。
1 class Vegetables (val toppings: List<String>) : Item("Vegetables" , 5 ) {
這不是最完美的解決方法,因為在 main()
中,您需要先變更程式碼來建立配料清單,然後才將其傳遞至 Vegetables
建構函式。
1 Vegetables(listOf("Cabbage" , "Sprouts" , "Onion" ))
還有更好的解決方法。
在 Kotlin 中,vararg
修改程式可讓您將類型相同、數量可變的引數傳遞給函式或建構函式。這樣一來,您就可以提供不同的蔬菜做為單個字串,而非 List。
變更 Vegetables
的類別定義,以採用類型為 String
的 vararg
toppings
。
1 class Vegetables (vararg val toppings: String) : Item("Vegetables" , 5 ) {
注意: 您只能將一個參數標示為 vararg
,且通常是 List 中的最後一個參數。
main()
函式中的程式碼現在正常運作。透過傳遞任何數量的配料字串,即可建立 Vegetables
例項。
1 2 3 4 5 fun main () { ... val vegetables = Vegetables("Cabbage" , "Sprouts" , "Onion" ) ... }
現在,請修改 Vegetables
類別的 toString()
方法,使其傳回同樣提及以下配料格式的 String
:Vegetables Cabbage, Sprouts, Onion
。
以商品名稱 (Vegetables
) 開頭。接著使用 joinToString()
方法將所有配料加入單一字串。使用 +
運算子將兩個部分加起來,之間留有空格。
1 2 3 4 5 class Vegetables (vararg val toppings: String) : Item("Vegetables" , 5 ) { override fun toString () : String { return name + " " + toppings.joinToString() } }
執行程式,輸出內容應為:
1 2 Noodles Vegetables Cabbage, Sprouts, Onion
注意: 如要指定半形逗號以外的分隔符,請將所需的分隔符字串做為引數傳遞至 joinToString()
方法。範例:joinToString(" ")
以空格分隔每個商品。
編寫程式時,您需要考量所有可能的輸入內容。如果 Vegetables
建構函式中沒有任何輸入引數,請使用較容易的方式處理 toString()
方法。
由於客戶想訂購蔬菜,但並未具體說明要哪些蔬菜,其中一種解決方法就是為他們提供預設由廚師選擇的蔬菜。
如未傳遞任何配料,請更新 toString()
方法以傳回 Vegetables Chef's Choice
。使用您先前學過的 isEmpty()
方法。
1 2 3 4 5 6 7 override fun toString () : String { if (toppings.isEmpty()) { return "$name Chef's Choice" } else { return name + " " + toppings.joinToString() } }
更新 main()
函數,以測試在兩種情況下建立 Vegetables
例項的可能性:不含任何建構函式引數以及含有多個引數。
1 2 3 4 5 6 7 8 fun main () { val noodles = Noodles() val vegetables = Vegetables("Cabbage" , "Sprouts" , "Onion" ) val vegetables2 = Vegetables() println(noodles) println(vegetables) println(vegetables2) }
確認輸出內容符合預期。
1 2 3 Noodles Vegetables Cabbage, Sprouts, Onion Vegetables Chef's Choice
建立一個訂單 現在您擁有了一些食物,可以建立訂單了。在程式的 Order
類別中封裝訂單的邏輯。
想想看 Order
類別可以使用哪些屬性和方法。如果有所幫助,請再次參考以下的最終程式碼輸出內容範例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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
。
1 class Order (val orderNumber: Int )
您可能無法預先知道訂單中的所有商品,因此無需將商品 List 作為引數傳遞。這可以宣告為頂層類別變數,並初始化為空白的 MutableList
,用於儲存 Item
類型的元素。標示變數 private
,讓系統只允許這個類別直接修改商品 List。這種做法可以防止此類別以外的程式碼意外修改清單。
1 2 3 class Order (val orderNumber: Int ) { private val itemList = mutableListOf<Item>() }
現在,請將方法加入類別定義中。您可以隨意為每種方法挑選合理的名稱,目前每個方法內的實作邏輯則可以留空。此外,還要決定需要哪些函式引數和傳回值。
1 2 3 4 5 6 7 8 9 10 11 12 class Order (val orderNumber: Int ) { private val itemList = mutableListOf<Item>() fun addItem (newItem: Item ) { } fun addAll (newItems: List <Item >) { } fun print () { } }
addItem()
方法似乎最直接,因此先實作該函式。該函式會擷取新的 Item
,而該方法應將其新增至 itemList
。
1 2 3 fun addItem (newItem: Item ) { itemList.add(newItem) }
接下來請實作 addAll()
方法。該方法會擷取唯讀商品 List。將所有商品加入內部商品 List。
1 2 3 fun addAll (newItems: List <Item >) { itemList.addAll(newItems) }
接著,請實作 print()
方法,藉此將所有商品及其價格的摘要以及訂單的總價輸出至輸出內容。 請先列印輸出訂單號碼。接下來,使用迴圈迭代訂單清單中的所有商品。請輸出每個商品及其對應價格。同時,保留到目前為止的總價,並在疊代整個清單時繼續增加總價。最後,輸出總價。嘗試自行實作這個邏輯。如需相關協助,請查看下列解決方法。
建議您加入貨幣符號,讓輸出內容更容易閱讀。以下是實作該解決方法的一個方式。此程式碼使用 $
貨幣符號,但也可以視需要換算成當地幣別符號。
1 2 3 4 5 6 7 8 9 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
例項,依此類推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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() }
上述程式碼的輸出內容應如下所示。確認總價已正確相加。
1 2 3 4 5 6 7 8 9 10 11 12 13 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
改善程式碼 保留訂單清單 如果您建構能實際在麵店中使用的程式,請務必追蹤所有客戶訂單的清單。
建立 List 以儲存所有訂單。它是唯讀 List 還是可變動 List 嗎?
將這段程式碼新增至 main()
函式。首先,請將 List 初始化為空白。接著,每次建立訂單時,將訂單加入 List 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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) }
由於訂單會隨著時間增加,因此 List 應為 Order
類型的 MutableList
。然後使用 MutableList
上的 add()
方法新增每個訂單。
建立訂單 List 後,您可以使用迴圈來列印每個訂單。在訂單之間列印空白行,讓輸出內容更容易閱讀。
1 2 3 4 5 6 7 8 9 10 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
。
1 2 3 4 5 6 7 8 9 fun addItem (newItem: Item ) : Order { itemList.add(newItem) return this } fun addAll (newItems: List <Item >) : Order { itemList.addAll(newItems) return this }
現在,在 main()
函式中,您可以將呼叫鏈結在一起,如以下程式碼所示。這個程式碼會建立新的 Order
,並充分運用建構工具模式。
1 2 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
。如果每種方法呼叫都自成一行,程式碼也更容易讀取。
1 2 3 4 5 ordersList.add( Order(5 ) .addItem(Noodles()) .addItem(Noodles()) .addItem(Vegetables("Spinach" )))
執行程式碼,以下是預期的輸出內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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
所有程式碼 以下是 Item
、Noodles
、Vegetables
和 Order
類別的程式碼。此外,main()
函式也會顯示這些類別的使用方式。實作這項程式的方法有很多,因此您的程式碼可能會略有不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 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>() 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) val order4 = Order(4 ) .addItem(Noodles()) .addItem(Vegetables("Cabbage" , "Onion" )) ordersList.add(order4) ordersList.add( Order(5 ) .addItem(Noodles()) .addItem(Noodles()) .addItem(Vegetables("Spinach" )) ) for (order in ordersList) { order.print() println() } }
總結
清單(List) 是特定類型元素的有序集合,例如 Strings
List。
索引(Index) 是反映元素位置的整數位置 (例如 myList[2]
)。
在 List 中,第一個元素位於索引 0
處 (例如 myList[0]
),最後一個元素則位於 myList.size-1
處 (例如 myList[myList.size-1]
或 myList.last()
)。
清單(List) 分為兩種類型:List
和 MutableList
。
List
處於唯讀狀態,在初始化後無法修改。不過,您可以執行 sorted()
和 reversed()
等作業,這些作業可在不變更原始 List 的情況下傳回新 List。
MutableList
建立後可以修改,例如新增、移除或修改元素。
您可以使用 addAll()
將商品清單新增至可變動 List。
使用 while
迴圈執行程式碼區塊,直到運算式評估為 false
並結束迴圈為止。1 2 3 4 5 while (expression) { // While the expression is true, execute this code block }
使用 for
迴圈疊代清單的所有商品:1 2 3 4 5 for (item in myList) { // Execute this code block for each element of the list }
vararg
修飾符可讓您將數量可變的引數傳遞給函式或建構函式。