Sử dụng List (Danh sách) trong Kotlin

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

1. Trước khi bạn bắt đầu

Thường thì bạn cần tạo danh sách cho rất nhiều tình huống trong cuộc sống hằng ngày, chẳng hạn như danh sách việc cần làm, danh sách khách mời cho một sự kiện, danh sách mong muốn hoặc danh sách thực phẩm cần mua. Trong lĩnh vực lập trình, danh sách cũng rất hữu ích. Ví dụ: trong một ứng dụng có thể có danh sách bài báo, bài hát, sự kiện trên lịch hoặc bài đăng trên mạng xã hội.

Việc tìm hiểu cách tạo và sử dụng danh sách là một kiến thức quan trọng trong lập trình mà bạn cần thêm vào bộ kỹ năng của mình. Kiến thức về danh sách sẽ giúp bạn tạo ra các ứng dụng phức tạp hơn.

Trong lớp học lập trình này, bạn sẽ sử dụng Kotlin Playground để làm quen với danh sách trong Kotlin, đồng thời tạo một chương trình để đặt hàng nhiều loại mì. Bạn đã thấy hấp dẫn chưa?

Điều kiện tiên quyết

  • Bạn đã quen thuộc với cách sử dụng Kotlin Playground để tạo và chỉnh sửa các chương trình Kotlin.
  • Bạn đã quen thuộc với các khái niệm cơ bản về lập trình Kotlin trong Bài 1 của khoá học Kiến thức cơ bản về lập trình Android bằng Kotlin: hàm main(), các đối số của hàm, giá trị trả về, biến, loại dữ liệu, thao tác và các câu lệnh luồng điều khiển.
  • Bạn biết cách khai báo lớp trong Kotlin, tạo một thực thể đối tượng từ lớp đó và truy cập vào các thuộc tính và phương thức của lớp.
  • Bạn có thể tạo các lớp con và hiểu cách các lớp con kế thừa lẫn nhau.

Kiến thức bạn sẽ học được

  • Cách tạo và sử dụng danh sách trong Kotlin
  • Sự khác biệt giữa ListMutableList, trường hợp nào thì nên sử dụng loại dữ liệu nào
  • Cách lặp lại tất cả mục trong danh sách và thực hiện hành động trên từng mục.

Sản phẩm bạn sẽ tạo ra

  • Bạn sẽ thử nghiệm các danh sách và thao tác trên danh sách trong Kotlin Playground.
  • Bạn sẽ tạo một chương trình đặt món ăn có sử dụng các danh sách trong Kotlin Playground.
  • Chương trình của bạn sẽ có khả năng tạo đơn đặt hàng, thêm món mì và rau củ, sau đó tính tổng chi phí của đơn đặt hàng đó.

Bạn cần có

2. Giới thiệu về List

Trong các lớp học lập trình trước, bạn đã tìm hiểu về các loại dữ liệu cơ bản trong Kotlin như Int, Double, BooleanString. Các loại dữ liệu này cho phép bạn lưu trữ một loại giá trị cụ thể trong một biến. Nhưng nếu bạn muốn lưu trữ nhiều giá trị thì sao? Trong trường hợp đó, việc có một loại dữ liệu List sẽ rất hữu ích.

Danh sách là tập hợp các mục có thứ tự cụ thể. Có hai loại danh sách trong Kotlin:

  • Danh sách chỉ đọc: List, bạn không thể chỉnh sửa loại danh sách này sau khi tạo.
  • Danh sách có thể thay đổi: MutableList, bạn sẽ chỉnh sửa được loại danh sách này sau khi tạo, nghĩa là bạn có thể thêm, xoá hoặc cập nhật các phần tử của danh sách đó.

Khi sử dụng List hoặc MutableList, bạn phải chỉ định loại phần tử có trong loại dữ liệu đó. Ví dụ: List<Int> chứa danh sách các giá trị số nguyên và List<String> chứa danh sách giá trị Chuỗi (String). Nếu khai báo một lớp Car trong chương trình của mình, bạn có thể có một List<Car> chứa danh sách các thực thể đối tượng Car.

Cách tốt nhất để hiểu về danh sách là thử sử dụng trên thực tế.

Tạo List

  1. Mở Kotlin Playground rồi xoá mã đang được cung cấp
  2. Thêm một hàm main() trống. Tất cả các bước thêm mã sau đây sẽ nằm trong hàm main() này.
fun main() {

}
  1. Bên trong main(), hãy tạo biến có tên numbers thuộc loại List<Int> vì biến này sẽ chứa danh sách chỉ đọc gồm các giá trị số nguyên. Tạo List mới bằng cách sử dụng hàm listOf() trong thư viện chuẩn của Kotlin, sau đó truyền vào các phần tử của danh sách dưới dạng các đối số được phân tách bằng dấu phẩy. listOf(1, 2, 3, 4, 5, 6) trả về danh sách chỉ đọc gồm các giá trị số nguyên từ 1 đến 6.
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
  1. Nếu có thể đoán (hoặc suy ra) loại biến dựa trên giá trị ở bên phải toán tử gán (=), thì bạn có thể bỏ qua loại dữ liệu của biến. Do đó, bạn có thể rút ngắn dòng mã này như sau:
val numbers = listOf(1, 2, 3, 4, 5, 6)
  1. Sử dụng println() để xuất danh sách numbers ra kết quả.
println("List: $numbers")

Hãy nhớ rằng khi đặt $ vào chuỗi, nội dung phía sau sẽ là biểu thức được đánh giá và thêm vào chuỗi này (xem mẫu cho chuỗi). Bạn cũng có thể viết dòng mã này dưới dạng println("List: " + numbers).

  1. Truy xuất kích thước của danh sách bằng cách sử dụng thuộc tính numbers.size rồi xuất giá trị đó ra kết quả.
println("Size: ${numbers.size}")
  1. Chạy chương trình Kết quả sau khi chạy là danh sách tất cả phần tử và kích thước của danh sách. Hãy lưu ý dấu ngoặc vuông [] cho biết đây là List (danh sách). Bên trong dấu ngoặc này là các phần tử của numbers, phân cách với nhau bằng dấu phẩy. Ngoài ra, hãy để ý rằng các phần tử nằm theo đúng thứ tự như bạn đã tạo.
List: [1, 2, 3, 4, 5, 6]
Size: 6

Truy cập các phần tử của danh sách

Chức năng mà chỉ danh sách mới có là cho phép bạn truy cập từng phần tử trong danh sách theo chỉ mục (index) của phần tử đó. Chỉ mục này là một số nguyên biểu thị vị trí của phần tử. Đây là sơ đồ của danh sách numbers mà chúng ta đã tạo, trong đó cho thầy từng phần tử và chỉ mục tương ứng với phần tử đó.

cb6924554804458d.png

Chỉ mục thực chất là số vị trí chênh lệch so với phần tử đầu tiên. Ví dụ: khi bạn nói list[2], thì nghĩa là bạn không yêu cầu phần tử thứ hai trong danh sách, mà yêu cầu phần tử nằm chênh lệch 2 vị trí so với phần tử đầu tiên. Do đó, list[0] là phần tử đầu tiên (chênh lệch là 0), list[1] là phần tử thứ hai (chênh lệch là 1), list[2] là phần tử thứ ba (chênh lệch là 2) và cứ tiếp tục như vậy.

Hãy thêm mã dưới đây vào sau mã hiện có trong hàm main(). Chạy mã đó sau mỗi bước để xác minh kết quả xuất ra là đúng với ý định của bạn.

  1. Xuất ra phần tử đầu tiên của danh sách tại chỉ mục 0. Bạn có thể gọi hàm get() có chỉ mục mong muốn dưới dạng numbers.get(0), hoặc sử dụng cú pháp nhanh có dấu ngoặc vuông xung quanh chỉ mục dưới dạng numbers[0].
println("First element: ${numbers[0]}")
  1. Tiếp theo, xuất ra phần tử thứ hai của danh sách tại chỉ mục 1.
println("Second element: ${numbers[1]}")

Các giá trị chỉ mục hợp lệ ("chỉ mục") của một danh sách đi từ 0 đến chỉ mục cuối cùng. Giá trị chỉ mục cuối cùng là kích thước của danh sách trừ đi 1. Như vậy có nghĩa là đối với danh sách numbers, giá trị chỉ mục sẽ đi từ 0 đến 5.

  1. Xuất ra phần tử cuối cùng của danh sách bằng cách sử dụng numbers.size - 1 để tính toán chỉ mục của phần tử đó, chỉ mục này sẽ là 5. Thao tác truy cập vào phần tử ở chỉ mục thứ 5 sẽ trả về kết quả là 6.
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
  1. Kotlin cũng hỗ trợ các thao tác first()last() trên một danh sách. Hãy thử gọi numbers.first()numbers.last() để xem kết quả.
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")

Bạn sẽ thấy rằng numbers.first() trả về phần tử đầu tiên của danh sách và numbers.last() trả về phần tử cuối cùng của danh sách.

  1. Một thao tác hữu ích khác cho danh sách là phương thức contains() để kiểm tra xem một phần tử cụ thể có nằm trong danh sách hay không. Ví dụ: nếu có danh sách tên nhân viên trong một công ty, bạn có thể sử dụng phương thức contains() để tìm hiểu xem một tên nào đó có trong danh sách đó hay không.

Trong danh sách numbers, hãy gọi phương thức contains() bằng một số nguyên có trong danh sách. numbers.contains(4) sẽ trả về giá trị true. Sau đó, hãy gọi phương thức contains() bằng một số nguyên không có trong danh sách của bạn. numbers.contains(7) sẽ trả về false.

println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
  1. Mã hoàn thiện của bạn sẽ có dạng như sau: Bạn không bắt buộc phải thêm phần ghi chú (comment).
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)}")
}
  1. Chạy mã của bạn. Đây là kết quả xuất ra.
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

Danh sách chỉ cho phép đọc

  1. Xoá mã trong Kotlin Playground rồi thay thế bằng mã sau. Danh sách colors được khởi tạo cho danh sách 3 màu sắc có dạng Strings.
fun main() {
    val colors = listOf("green", "orange", "blue")
}
  1. Hãy nhớ rằng bạn không thể thêm hoặc thay đổi các phần tử trong List chỉ đọc. Hãy xem điều gì xảy ra nếu bạn cố gắng thêm một mục vào danh sách hoặc cố gắng chỉnh sửa một phần tử của danh sách bằng cách thiết lập giá trị mới cho phần tử đó.
colors.add("purple")
colors[0] = "yellow"
  1. Sau khi chạy mã, bạn sẽ nhận được một số thông báo lỗi. Về cơ bản, các lỗi này cho biết rằng phương thức add() không tồn tại đối với List và bạn không được phép thay đổi giá trị của phần tử.

dd21aaccdf3528c6.png

  1. Hãy xoá mã không chính xác.

Bạn đã trực tiếp thấy rằng không thể thay đổi danh sách chỉ đọc. Tuy nhiên, có một số thao tác trên danh sách sẽ không làm thay đổi danh sách đó mà trả về danh sách mới. Trong đó có hai thao tác là reversed()sorted(). Hàm reversed() trả về một danh sách mới, trong đó các phần tử được sắp xếp theo thứ tự đảo ngược và sorted() trả về danh sách mới, trong đó các phần tử được sắp xếp theo thứ tự tăng dần.

  1. Thêm mã để đảo ngược thứ tự trong danh sách colors. Xuất kết quả ra. Đây là danh sách mới chứa các phần tử của colors theo thứ tự đảo ngược.
  2. Thêm dòng mã thứ hai để xuất list ban đầu ra kết quả, từ đó bạn có thể thấy danh sách ban đầu không thay đổi.
println("Reversed list: ${colors.reversed()}")
println("List: $colors")
  1. Đây là kết quả của hai danh sách đã xuất.
Reversed list: [blue, orange, green]
List: [green, orange, blue]
  1. Thêm mã để trả về phiên bản được sắp xếp của List bằng cách sử dụng hàm sorted().
println("Sorted list: ${colors.sorted()}")

Kết quả là danh sách mới gồm các màu được sắp xếp theo thứ tự bảng chữ cái. Thật thú vị!

Sorted list: [blue, green, orange]
  1. Bạn cũng có thể thử dùng hàm sorted() trên danh sách các số chưa được sắp xếp.
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]

Đến đây, bạn đã thấy được tác dụng của việc biết cách tạo danh sách. Tuy nhiên, sẽ càng tốt hơn nếu bạn có thể chỉnh sửa danh sách sau khi tạo. Hãy xem phần tiếp theo để tìm hiểu về danh sách có thể thay đổi.

3. Giới thiệu về danh sách có thể thay đổi (Mutable List)

Danh sách có thể thay đổi là danh sách cho phép chỉnh sửa sau khi tạo. Bạn có thể thêm, xoá hoặc thay đổi các mục trong danh sách. Bạn cũng có thể thực hiện mọi thao tác áp dụng cho danh sách chỉ đọc. Danh sách có thể thay đổi thuộc loại MutableList và bạn có thể tạo danh sách này bằng cách gọi mutableListOf().

Tạo MutableList

  1. Xoá mã hiện tại trong main().
  2. Trong hàm main(), hãy tạo một danh sách trống có thể thay đổi rồi gán danh sách đó cho biến val tên là entrees.
val entrees = mutableListOf()

Nếu bạn chạy mã này thì sẽ dẫn đến lỗi sau.

Not enough information to infer type variable T

Như đã đề cập ở trên, khi bạn tạo MutableList hoặc List, Kotlin sẽ cố gắng suy luận loại dữ liệu của phần tử trong danh sách dựa trên các đối số truyền vào. Ví dụ: nếu bạn viết listOf("noodles"), Kotlin suy luận rằng bạn muốn tạo một danh sách loại String. Khi bạn khởi tạo một danh sách trống không có phần tử, Kotlin không thể suy luận ra loại dữ liệu của phần tử, vì vậy bạn phải nêu rõ loại dữ liệu đó. Bạn có thể thực hiện bằng cách thêm cặp dấu nhọn <> ngay sau mutableListOf hoặc listOf. (Trong tài liệu này, bạn có thể thấy mã này có dạng <T>, trong đó T đại diện cho tham số loại dữ liệu).

  1. Hãy chỉnh sửa khai báo biến để chỉ định rằng bạn muốn tạo danh sách có thể thay đổi thuộc loại String.
val entrees = mutableListOf<String>()

Một cách khác để khắc phục lỗi này là chỉ định trước loại dữ liệu của biến.

val entrees: MutableList<String> = mutableListOf()
  1. Xuất danh sách ra kết quả.
println("Entrees: $entrees")
  1. Kết quả xuất ra cho thấy [] cho một danh sách trống.
Entrees: []

Thêm phần tử vào danh sách

Danh sách có thể thay đổi trở nên thú vị khi bạn thêm, xoá và cập nhật phần tử.

  1. Thêm "noodles" vào danh sách bằng entrees.add("noodles"). Hàm add() sẽ trả về true nếu phần tử được thêm thành công vào danh sách, trả về false nếu không thành công.
  2. Xuất danh sách ra kết quả để xác nhận rằng "noodles" đã thực sự được thêm vào.
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")

Kết quả xuất ra là:

Add noodles: true
Entrees: [noodles]
  1. Thêm một mục "spaghetti" vào danh sách.
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")

Danh sách entrees hiện tại chứa hai mục.

Add spaghetti: true
Entrees: [noodles, spaghetti]

Thay vì thêm từng phần tử bằng add(), bạn có thể thêm nhiều phần tử cùng lúc bằng cách sử dụng addAll() và truyền vào một danh sách.

  1. Tạo danh sách moreItems. Bạn sẽ không cần thay đổi danh sách này nên hãy thiết lập nó thành biến val không thể thay đổi.
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
  1. Sử dụng addAll() để thêm tất cả các mục từ danh sách mới vào entrees. Xuất danh sách sau khi thêm.
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")

Kết quả xuất ra cho thấy việc thêm danh sách đã thành công. Danh sách entrees nay có tổng cộng 5 mục.

Add list: true
Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]
  1. Bây giờ, hãy thử thêm một con số vào danh sách này.
entrees.add(10)

Thao tác này không thành công do gặp lỗi:

The integer literal does not conform to the expected type String

Lỗi này xảy ra vì danh sách entrees là dành cho các phần tử thuộc loại String nhưng bạn lại đang cố thêm Int. Hãy nhớ chỉ thêm các phần tử thuộc đúng loại dữ liệu vào danh sách. Nếu không, bạn sẽ gặp lỗi biên dịch. Đây là một cách để Kotlin đảm bảo mã của bạn an toàn hơn thông qua tính an toàn với loại dữ liệu.

  1. Xoá dòng mã không chính xác để mã của bạn biên dịch thành công.

Xoá phần tử khỏi danh sách

  1. Gọi remove() để xoá "spaghetti" khỏi danh sách. Xuất danh sách này ra kết quả.
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
  1. Thao tác xoá "spaghetti" sẽ trả về giá trị true (đúng) vì phần tử này có trong danh sách và có thể xoá được. Danh sách này hiện chỉ còn 4 mục.
Remove spaghetti: true
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. Điều gì xảy ra nếu bạn cố gắng xoá một mục không tồn tại trong danh sách? Hãy thử xoá "rice" khỏi danh sách bằng entrees.remove("rice").
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")

Phương thức remove() trả về false vì phần tử không tồn tại và do đó không thể xoá. Danh sách này vẫn còn 4 mục như trước đó. Kết quả xuất ra:

Remove item that doesn't exist: false
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. Bạn cũng có thể chỉ định chỉ mục của phần tử cần xoá. Sử dụng removeAt() để xoá mục tại chỉ mục 0.
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")

Giá trị trả về của removeAt(0) là phần tử đầu tiên ("noodles") và phần tử này đã bị xoá khỏi danh sách. Danh sách entrees nay còn 3 mục.

Remove first element: noodles
Entrees: [ravioli, lasagna, fettuccine]
  1. Nếu muốn xoá toàn bộ danh sách, bạn có thể gọi clear().
entrees.clear()
println("Entrees: $entrees")

Kết quả xuất ra cho thấy một danh sách trống.

Entrees: []
  1. Kotlin cho phép bạn kiểm tra xem danh sách có trống hay không bằng cách sử dụng hàm isEmpty(). Hãy thử xuất entrees.isEmpty(). ra kết quả
println("Empty? ${entrees.isEmpty()}")

Kết quả phải là giá trị true (đúng) vì danh sách hiện đang trống với 0 phần tử.

Empty? true

isEmpty() là một phương thức hữu ích nếu bạn muốn thực hiện một thao tác trên danh sách hoặc muốn truy cập một phần tử nhất định, nhưng trước hết, bạn phải đảm bảo danh sách này không trống.

Dưới đây là toàn bộ mã bạn đã viết cho danh sách có thể thay đổi. Bạn không bắt buộc phải thêm phần ghi chú (comment).

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. Sử dụng vòng lặp câu lệnh cho List

Để thực hiện một thao tác với từng mục trong danh sách, bạn có thể sử dụng vòng lặp (loop) câu lệnh cho danh sách (còn gọi là lặp lại câu lệnh suốt toàn bộ danh sách). Bạn có thể thực hiện vòng lặp câu lệnh bằng ListsMutableLists.

Vòng lặp While

Có một loại vòng lặp câu lệnh là while. Vòng lặp while bắt đầu bằng từ khoá while trong Kotlin. Vòng lặp này chứa một khối mã (trong dấu ngoặc nhọn) được thực thi nhiều lần liên tục, chỉ cần biểu thức trong dấu ngoặc vẫn mang giá trị true. Để ngăn mã thực thi mãi mãi (gọi là một vòng lặp vô hạn), khối mã phải chứa logic có khả năng thay đổi giá trị của biểu thức, để cuối cùng biểu thức sẽ có giá trị false (sai) và bạn ngừng thực thi vòng lặp. Khi đó, bạn sẽ thoát khỏi vòng lặp while và tiếp tục thực thi mã nằm sau vòng lặp.

while (expression) {
    // While the expression is true, execute this code block
}

Sử dụng vòng lặp while để lặp lại câu lệnh suốt toàn bộ danh sách. Tạo một biến index để theo dõi xem bạn đang nhìn thấy vị trí chỉ mục nào trong danh sách. Biến index này sẽ tăng lên 1 sau mỗi lần lặp cho đến khi bạn đạt đến chỉ mục cuối cùng của danh sách, sau đó bạn sẽ thoát khỏi vòng lặp.

  1. Xoá mã hiện có trong Kotlin Playground và dùng một hàm main() trống.
  2. Giả sử bạn đang tổ chức một bữa tiệc. Hãy tạo một danh sách mà trong đó mỗi phần tử đại diện cho số lượng khách trong mỗi gia đình đã hồi đáp lời mời của bạn. Gia đình đầu tiên cho biết 2 người trong gia đình họ sẽ đến dự. Gia đình thứ hai cho biết 4 người trong gia đình họ sẽ đến dự và cứ tiếp tục như vậy cho đến hết.
val guestsPerFamily = listOf(2, 4, 1, 3)
  1. Xác định tổng số khách sẽ đến dự. Viết một vòng lặp để tìm câu trả lời. Tạo var cho tổng số khách rồi gán giá trị ban đầu là 0.
var totalGuests = 0
  1. Khởi tạo var cho biến index, như mô tả ở trên.
var index = 0
  1. Viết vòng lặp while để lặp lại câu lệnh cho danh sách. Điều kiện là tiếp tục thực thi khối mã, chỉ cần giá trị index vẫn nhỏ hơn kích thước của danh sách.
while (index < guestsPerFamily.size) {

}
  1. Trong vòng lặp, hãy lấy phần tử tại index (chỉ mục) hiện tại của danh sách và thêm phần tử đó vào tổng số khách. Hãy nhớ rằng totalGuests += guestsPerFamily[index] giống với totalGuests = totalGuests + guestsPerFamily[index].

Hãy lưu ý rằng dòng cuối cùng của vòng lặp sẽ tăng biến index thêm 1 bằng cách sử dụng index++, nhằm tiếp tục chạy vòng lặp cho gia đình tiếp theo trong danh sách.

while (index < guestsPerFamily.size) {
    totalGuests += guestsPerFamily[index]
    index++
}
  1. Sau khi vòng lặp while kết thúc, bạn có thể xuất kết quả ra.
while ... {
    ...
}
println("Total Guest Count: $totalGuests")
  1. Chạy chương trình và kết quả xuất ra sẽ như sau. Bạn có thể xác minh tính chính xác của câu trả lời bằng cách thêm các số trong danh sách theo cách thủ công.
Total Guest Count: 10

Dưới đây là đoạn mã đầy đủ:

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")

Với vòng lặp while, bạn phải viết mã để tạo một biến nhằm theo dõi chỉ mục, lấy phần tử tại chỉ mục đó trong danh sách và cập nhật biến chỉ mục đó. Có một cách thậm chí còn nhanh hơn và ngắn gọn hơn để lặp lại câu lệnh cho danh sách. Đó là sử dụng vòng lặp for!

Vòng lặp For

for là cũng là một loại vòng lặp. Vòng lặp này giúp cho việc lặp lại câu lệnh trên danh sách trở nên dễ dàng hơn nhiều. Vòng lặp này bắt đầu bằng từ khoá for trong Kotlin, đi kèm khối mã đặt trong dấu ngoặc nhọn. Điều kiện để thực thi khối mã được nêu trong dấu ngoặc đơn.

for (number in numberList) {
   // For each element in the list, execute this code block
}

Trong ví dụ này, giá trị của biến number được thiết lập bằng với phần tử đầu tiên của numberList và khối mã được thực thi. Sau đó, biến number sẽ tự động cập nhật giá trị thành phần tử tiếp theo của numberList và khối mã được thực thi lại. Quá trình này lặp lại cho mỗi phần tử của danh sách, cho đến khi đạt đến cuối numberList.

  1. Xoá mã hiện có trong Kotlin Playground rồi thay thế bằng mã sau:
fun main() {
    val names = listOf("Jessica", "Henry", "Alicia", "Jose")
}
  1. Thêm một vòng lặp for để xuất tất cả các mục trong danh sách names ra kết quả.
for (name in names) {
    println(name)
}

Cách này dễ hơn rất nhiều so với việc bạn phải viết một vòng lặp while!

  1. Kết quả xuất ra là:
Jessica
Henry
Alicia
Jose

Một thao tác phổ biến đối với danh sách là thực hiện một lệnh nào đó với từng phần tử của danh sách.

  1. Chỉnh sửa vòng lặp để xuất ra số ký tự trong tên từng người. Gợi ý: bạn có thể sử dụng thuộc tính length của String để tìm số lượng ký tự trong String đó.
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
for (name in names) {
    println("$name - Number of characters: ${name.length}")
}

Kết quả xuất ra:

Jessica - Number of characters: 7
Henry - Number of characters: 5
Alicia - Number of characters: 6
Jose - Number of characters: 4

Mã trong vòng lặp không thay đổi List ban đầu. Mã này chỉ ảnh hưởng đến kết quả xuất ra màn hinh.

Thật tuyệt vời khi bạn có thể viết câu lệnh vốn chỉ dành cho một mục trong danh sách và mã đó được thực thi cho mọi mục trong danh sách! Việc sử dụng vòng lặp có thể giúp bạn không phải nhập đi nhập lại cùng một đoạn mã.

Đến nay, bạn đã thử tạo và sử dụng cả danh sách thường và danh sách có thể thay đổi, cũng như đã tìm hiểu về vòng lặp. Giờ đã đến lúc bạn áp dụng kiến thức này vào trường hợp sử dụng!

5. Kết hợp kiến thức đã học

Khi đặt đồ ăn tại một nhà hàng địa phương, khách hàng thường có nhiều món trong một đơn đặt hàng. Bạn nên sử dụng danh sách để lưu trữ thông tin về đơn đặt hàng. Bạn cũng sẽ trau dồi kiến thức về lớp dữ liệu và tính kế thừa để tạo một chương trình Kotlin vừa mạnh mẽ vừa có thể mở rộng quy mô, thay vì đặt tất cả mã trong hàm main().

Trong các nhiệm vụ tiếp theo, hãy tạo một chương trình Kotlin cho phép đặt nhiều món ăn.

Trước tiên, hãy xem kết quả mẫu dưới đây của mã khi đã hoàn thiện. Bạn có thể hình dung ra các lớp dữ liệu mà mình sẽ phải tạo để sắp xếp tất cả những dữ liệu này không?

Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Từ kết quả xuất ra, bạn sẽ nhận thấy rằng:

  • có một danh sách đơn đặt hàng
  • mỗi đơn đặt hàng có một mã số
  • mỗi đơn đặt hàng có thể chứa một danh sách các mặt hàng như mì và rau
  • mỗi mặt hàng đều có giá
  • mỗi đơn đặt hàng có một tổng giá, tức là giá của tất cả mặt hàng cộng lại

Bạn có thể tạo lớp Order đại diện cho một đơn hàng và một lớp đại diện cho từng mặt hàng thực phẩm như Noodles hoặc Vegetables. Bạn cũng có thể nhận ra rằng NoodlesVegetables có một số điểm giống nhau vì cả hai đều là các mặt hàng thực phẩm và mỗi mặt hàng đều có giá. Bạn có thể cân nhắc tạo một lớp Item có các thuộc tính dùng chung mà cả lớp Noodle và lớp Vegetable đều có thể kế thừa. Bằng cách này, bạn không cần sao chép logic trong cả lớp Noodle và lớp Vegetable.

  1. Bạn sẽ được cung cấp mã khởi động như sau. Các nhà phát triển chuyên nghiệp thường phải đọc mã của người khác, ví dụ như khi họ tham gia dự án mới hoặc thêm một tính năng do người khác tạo. Khả năng đọc và hiểu mã là một kỹ năng quan trọng.

Hãy dành chút thời gian để xem qua mã này và tìm hiểu cách hoạt động của nó. Sao chép và dán mã này vào Kotlin Playground rồi chạy mã này. Hãy nhớ xoá mọi mã hiện có trong Kotlin Playground trước khi dán mã mới này. Quan sát kết quả để xem liệu bạn có hiểu rõ mã này hơn không.

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. Bạn sẽ thấy kết quả xuất ra có dạng như sau:
Noodles@5451c3a8
Vegetables@76ed5528

Dưới đây là phần giải thích chi tiết hơn về mã này. Đầu tiên, có một lớp được gọi là Item, trong đó hàm khởi tạo của lớp này chứa 2 tham số: name biểu thị tên mặt hàng (dạng Chuỗi) và price biểu thị giá mặt hàng (dạng số nguyên). Cả hai thuộc tính này đều không thay đổi sau khi được truyền vào, vì vậy các thuộc tính này được đánh dấu là val. Vì Item là lớp gốc và các lớp con được mở rộng từ lớp đó, nên lớp gốc này được đánh dấu bằng từ khoá open.

Hàm khởi tạo lớp Noodles không chứa tham số mà mở rộng từ Item và gọi hàm khởi tạo lớp cao cấp bằng cách truyền vào "Noodles" dưới dạng tên và giá là 10. Lớp Vegetables cũng tương tự nhưng lớp này gọi hàm khởi tạo lớp cao cấp bằng "Vegetables" và giá là 5.

Hàm main() khởi tạo các thực thể đối tượng mới của lớp NoodlesVegetables, sau đó xuất ra kết quả.

Ghi đè phương thức toString()

Khi bạn xuất một thực thể đối tượng ra kết quả, phương thức toString() của đối tượng sẽ được gọi. Trong Kotlin, tất cả các lớp đều tự động kế thừa phương thức toString(). Khi triển khai theo cách mặc định, phương thức này chỉ trả về loại đối tượng có địa chỉ bộ nhớ cho thực thể này. Bạn nên ghi đè toString() để trả về một giá trị có ý nghĩa và thân thiện với người dùng hơn là Noodles@5451c3a8Vegetables@76ed5528.

  1. Bên trong lớp Noodles, hãy ghi đè phương thức toString() và yêu cầu phương thức này trả về name. Hãy nhớ rằng Noodles kế thừa thuộc tính name từ lớp gốc Item của thuộc tính này.
class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}
  1. Lặp lại tương tự cho lớp Vegetables.
class Vegetables() : Item("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}
  1. Chạy mã của bạn. Kết quả hiện đã dễ hiểu hơn:
Noodles
Vegetables

Trong bước tiếp theo, bạn sẽ thay đổi hàm khởi tạo lớp Vegetables để chứa một số tham số và cập nhật phương thức toString() để thể hiện thông tin bổ sung đó.

Chọn rau củ trong đơn đặt hàng

Để món mì sợi trở nên đặc sắc hơn, bạn có thể thêm nhiều loại rau củ vào đơn đặt hàng.

  1. Trong hàm main(), thay vì khởi tạo thực thể Vegetables mà không có đối số đầu vào, hãy truyền vào giá trị là các loại rau cụ thể mà khách hàng muốn.
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}

Nếu bạn cố gắng biên dịch mã của mình ngay bây giờ, sẽ có một lỗi như sau:

Too many arguments for public constructor Vegetables() defined in Vegetables

Bạn hiện đang truyền 3 đối số Chuỗi vào hàm khởi tạo lớp Vegetables, vì vậy, bạn sẽ phải chỉnh sửa lớp Vegetables.

  1. Cập nhật tiêu đề lớp Vegetables để chứa 3 tham số dạng chuỗi, như trong mã sau:
class Vegetables(val topping1: String,
                 val topping2: String,
                 val topping3: String) : Item ("Vegetables", 5) {
  1. Bây giờ, mã của bạn sẽ biên dịch lại. Tuy nhiên, giải pháp này chỉ hiệu quả nếu khách hàng của bạn luôn muốn đặt mua đúng ba loại rau củ. Nếu muốn đặt 1 hoặc 5 loại rau thì khách hàng sẽ không được như ý.
  2. Thay vì sử dụng một thuộc tính cho mỗi loại rau, bạn có thể khắc phục vấn đề này bằng cách chấp nhận một danh sách các loại rau (có độ dài bất kỳ) trong hàm khởi tạo cho lớp Vegetables. List chỉ được chứa Strings. Do đó, loại tham số đầu vào là List<String>.
class Vegetables(val toppings: List<String>) : Item("Vegetables", 5) {

Đây không phải là giải pháp tốt nhất vì trong main(), bạn sẽ phải thay đổi mã của mình để tạo danh sách các món ăn kèm trước khi truyền vào hàm khởi tạo Vegetables.

Vegetables(listOf("Cabbage", "Sprouts", "Onion"))

Có một cách hay hơn nữa để giải quyết vấn đề này.

  1. Trong Kotlin, biến bổ trợ vararg cho phép bạn truyền nhiều đối số cùng loại vào một hàm hoặc hàm khởi tạo, số lượng đối số có thể thay đổi. Bằng cách này, bạn có thể cung cấp các loại rau dưới dạng chuỗi riêng lẻ thay vì danh sách.

Thay đổi khai báo lớp của Vegetables để lấy mộtvararg toppings thuộc loại String.

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
  1. Mã trong hàm main() giờ sẽ hoạt động. Bạn có thể tạo một thực thể Vegetables bằng cách truyền vào một số lượng bất kỳ các món ăn kèm ở dạng Chuỗi.
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}
  1. Bây giờ, hãy chỉnh sửa phương thức toString() của lớp Vegetables sao cho phương thức này trả về một String cũng đề cập đến các món ăn kèm ở định dạng sau: Vegetables Cabbage, Sprouts, Onion.

Hãy bắt đầu từ tên của Item (Vegetables). Sau đó, dùng phương thức joinToString() để đưa tất cả các món ăn kèm vào một chuỗi duy nhất. Hãy nối hai phần này với nhau bằng toán tử + có dấu cách ở giữa.

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        return name + " " + toppings.joinToString()
    }
}
  1. Hãy chạy chương trình của bạn và kết quả xuất ra sẽ là:
Noodles
Vegetables Cabbage, Sprouts, Onion
  1. Khi viết chương trình, bạn cần xem xét tất cả thông tin đầu vào có thể có. Khi không có đối số đầu vào cho hàm khởi tạo Vegetables, hãy xử lý phương thức toString() theo cách thân thiện hơn với người dùng.

Vì khách hàng muốn mua rau mà không nói loại rau nào, nên một giải pháp là đưa ra món rau mặc định theo lựa chọn của đầu bếp.

Hãy cập nhật phương thức toString() để trả về Vegetables Chef's Choice nếu không có món ăn kèm nào được truyền vào. Sử dụng phương thức isEmpty() mà bạn đã tìm hiểu trước đó.

override fun toString(): String {
    if (toppings.isEmpty()) {
        return "$name Chef's Choice"
    } else {
        return name + " " + toppings.joinToString()
    }
}
  1. Cập nhật hàm main() để thử cả hai cách tạo thực thể Vegetables, một cách không có đối số nào trong hàm khởi tạo và cách còn lại có một số đối số.
fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    val vegetables2 = Vegetables()
    println(noodles)
    println(vegetables)
    println(vegetables2)
}
  1. Xác minh xem kết quả có như dự kiến hay không.
Noodles
Vegetables Cabbage, Sprouts, Onion
Vegetables Chef's Choice

Tạo đơn đặt hàng

Vì đã có một số mặt hàng thực phẩm nên bạn có thể tạo một đơn đặt hàng (order). Tìm hiểu logic của một đơn đặt hàng trong lớp Order trong chương trình của bạn.

  1. Hãy nghĩ về các thuộc tính và phương thức phù hợp cho lớp Order. Sau đây là một số kết quả mẫu từ mã hoàn thiện để bạn tham khảo.
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
  1. Có thể bạn đã nghĩ ra những nội dung sau đây:

Lớp đơn đặt hàng

Thuộc tính: mã số đơn đặt hàng, danh sách mặt hàng

Phương thức: thêm mặt hàng, thêm nhiều mặt hàng, xuất nội dung tóm tắt đơn đặt hàng (bao gồm giá)

  1. Trước tiên, hãy tập trung vào các thuộc tính. Bạn nên chọn loại dữ liệu nào cho mỗi thuộc tính? Các thuộc tính này nên ở chế độ công khai hay riêng tư với lớp dữ liệu? Nên truyền các thuộc tính này vào dưới dạng đối số hay nên khai báo các thuộc tính này bên trong lớp?
  2. Có nhiều cách để triển khai nhưng sau đây là một giải pháp bạn có thể áp dụng. Hãy tạo class Order có tham số hàm khởi tạo orderNumber là số nguyên.
class Order(val orderNumber: Int)
  1. Vì có thể bạn không biết trước tất cả mặt hàng trong đơn đặt hàng, nên đừng yêu cầu truyền danh sách các mặt hàng vào dưới dạng đối số. Thay vào đó, bạn có thể khai báo dưới dạng biến lớp cấp cao nhất và khởi chạy dưới dạng MutableList trống có thể chứa các phần tử thuộc loại Item. Đánh dấu biến này là private để chỉ lớp này mới có thể chỉnh sửa trực tiếp danh sách các mặt hàng đó. Việc này sẽ bảo vệ danh sách không bị chỉnh sửa ngoài ý muốn bằng mã bên ngoài lớp này.
class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()
}
  1. Tiếp tục thêm các phương thức vào phần khai báo lớp. Hãy chọn tên hợp lý cho mỗi phương thức và bạn có thể để trống logic triển khai hiện tại trong mỗi phương thức. Ngoài ra, bạn cần xác định các đối số hàm và giá trị trả về cần có.
class Order(val orderNumber: Int) {
   private val itemList = mutableListOf<Item>()

   fun addItem(newItem: Item) {
   }

   fun addAll(newItems: List<Item>) {
   }

   fun print() {
   }
}
  1. Phương thức addItem() có vẻ đơn giản nhất, vì vậy, hãy triển khai hàm đó trước. Hàm này nhận một Item mới rồi phương thức này sẽ thêm nó vào itemList.
fun addItem(newItem: Item) {
    itemList.add(newItem)
}
  1. Tiếp theo, hãy triển khai phương thức addAll(). Phương thức này nhận danh sách chỉ đọc gồm các mặt hàng. Thêm tất cả mặt hàng đó vào danh sách mặt hàng nội bộ.
fun addAll(newItems: List<Item>) {
    itemList.addAll(newItems)
}
  1. Sau đó, hãy triển khai phương thức print() để xuất ra kết quả bao gồm thông tin tóm tắt về tất cả mặt hàng, giá của các mặt hàng đó và tổng giá của đơn đặt hàng.

Trước tiên, hãy xuất mã số đơn đặt hàng. Sau đó, hãy dùng vòng lặp để lặp lại thao tác cho tất cả các mục trong danh sách đơn đặt hàng. Xuất từng mặt hàng và giá tương ứng của mặt hàng đó ra kết quả. Ngoài ra, hãy giữ lại tổng giá cho đến thời điểm hiện tại và tiếp tục thêm giá bán khi bạn lặp lại danh sách này. Xuất tổng giá cuối ra kết quả. Hãy cố gắng tự triển khai logic này. Nếu bạn cần trợ giúp, hãy xem giải pháp bên dưới.

Bạn nên thêm ký hiệu tiền tệ để kết quả dễ đọc hơn. Sau đây là một cách triển khai giải pháp này. Mã này sử dụng ký hiệu đơn vị tiền tệ $, nhưng bạn có thể chỉnh sửa thành ký hiệu nội tệ của quốc gia mình.

fun print() {
    println("Order #${orderNumber}")
    var total = 0
    for (item in itemList) {
        println("${item}: $${item.price}")
        total += item.price
    }
    println("Total: $${total}")
}

Đối với mỗi item trong itemList, hãy xuất item (việc này sẽ kích hoạt lệnh gọi toString() trên item) rồi đến price của mặt hàng đó. Ngoài ra, trước vòng lặp, hãy khởi tạo biến số nguyên total có giá trị 0. Sau đó, hãy thêm tổng số tiền bằng cách thêm giá của mặt hàng hiện tại vào total.

Tạo đơn đặt hàng

  1. Hãy kiểm tra mã bằng cách tạo các thực thể Order trong hàm main(). Trước tiên, hãy xoá mã hiện có trong hàm main().
  2. Bạn có thể sử dụng các đơn đặt hàng mẫu này hoặc tạo đơn đặt hàng của riêng mình. Thử chạy mã theo nhiều tổ hợp mặt hàng trong một đơn đặt hàng, đảm bảo rằng bạn chạy thử tất cả đường dẫn mã trong mã của mình. Ví dụ: thử các phương thức addItem()addAll() trong lớp Order, tạo các thực thể Vegetables có đối số hoặc không có đối số, v.v.
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. Kết quả xuất ra cho mã ở trên sẽ có dạng như sau. Xác minh xem tổng giá có đúng hay không.
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

Tuyệt vời! Kết quả giờ đã giống một đơn đặt món ăn!

6. Cải thiện mã

Lập danh sách đơn đặt hàng

Nếu đang xây dựng một chương trình để phục vụ một tiệm mì trên thực tế, thì bạn nên theo dõi danh sách tất cả đơn đặt hàng của khách hàng.

  1. Tạo một danh sách để lưu trữ tất cả đơn đặt hàng. Đó là danh sách chỉ đọc hay danh sách có thể thay đổi?
  2. Thêm mã này vào hàm main(). Ban đầu, hãy khởi tạo danh sách trống. Sau đó, khi mỗi đơn đặt hàng được tạo, hãy thêm đơn đặt hàng đó vào danh sách.
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)
}

Vì các đơn đặt hàng được thêm dần theo thời gian, nên danh sách phải là MutableList thuộc loại Order. Sau đó, hãy sử dụng phương thức add() trên MutableList để thêm từng đơn đặt hàng.

  1. Sau khi có danh sách đơn đặt hàng, bạn có thể sử dụng vòng lặp để xuất kết quả từng đơn đặt hàng. Xuất một dòng trống giữa các đơn đặt hàng để kết quả dễ đọc hơn.
fun main() {
    val ordersList = mutableListOf<Order>()

    ...

    for (order in ordersList) {
        order.print()
        println()
    }
}

Cách làm này sẽ xoá mã trùng lặp trong hàm main() và giúp mã dễ đọc hơn! Kết quả xuất ra sẽ giống như trước.

Triển khai mẫu Builder cho đơn đặt hàng

Để mã Kotlin của bạn ngắn gọn hơn, bạn có thể dùng mẫu Builder để tạo đơn đặt hàng. Mẫu Builder là một mẫu thiết kế trong lập trình, cho phép bạn tạo một đối tượng phức tạp theo từng bước một.

  1. Thay vì trả về Unit (hoặc không trả về giá trị nào) từ phương thức addItem()addAll() trong lớp Order, hãy trả về Order đã được thay đổi. Kotlin cung cấp từ khoá this để tham chiếu đến thực thể đối tượng hiện tại. Trong phương thức addItem()addAll(), bạn trả về Order hiện tại bằng cách trả về this.
fun addItem(newItem: Item): Order {
    itemList.add(newItem)
    return this
}

fun addAll(newItems: List<Item>): Order {
    itemList.addAll(newItems)
    return this
}
  1. Lúc này, trong hàm main(), bạn có thể liên kết các lệnh gọi với nhau, như minh hoạ trong mã sau. Mã này tạo một Order mới và tận dụng lợi ích của mẫu Builder.
val order4 = Order(4).addItem(Noodles()).addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)

Order(4) trả về một thực thể Order. Bạn có thể gọi addItem(Noodles()) trên thực thể này. Phương thức addItem() trả về cùng một thực thể Order (có trạng thái mới) và bạn có thể gọi lại addItem() trên thực thể đó bằng giá trị là các loại rau. Kết quả Order trả về có thể được lưu trữ trong biến order4.

Mã hiện có để tạo Orders vẫn hoạt động nên bạn không cần thay đổi mã đó. Mặc dù bạn không bắt buộc phải liên kết các lệnh gọi này, nhưng đây là một phương pháp phổ biến và nên dùng để tận dụng giá trị trả về của hàm.

  1. Đến thời điểm này, bạn thậm chí không cần lưu trữ đơn đặt hàng trong biến. Trong main() (trước vòng lặp cuối cùng để xuất các đơn đặt hàng), hãy trực tiếp tạo Order và thêm nó vào orderList. Mã này cũng dễ đọc hơn nếu mỗi lệnh gọi phương thức nằm trên một dòng riêng.
ordersList.add(
    Order(5)
        .addItem(Noodles())
        .addItem(Noodles())
        .addItem(Vegetables("Spinach")))
  1. Chạy mã và đây là kết quả dự kiến:
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

Chúc mừng bạn đã hoàn thành lớp học lập trình này!

Giờ đây bạn đã biết lợi ích của việc lưu trữ dữ liệu trong danh sách, thay đổi danh sách và triển khai vòng lặp trên danh sách. Hãy áp dụng kiến thức này cho ứng dụng Android để hiện danh sách dữ liệu trên màn hình trong lớp học lập trình tiếp theo!

7. Mã giải pháp

Dưới đây là mã giải pháp cho lớp Item, Noodles, VegetablesOrder. Hàm main() cũng cho biết cách sử dụng các lớp đó. Có nhiều cách để triển khai chương trình này nên mã của bạn có thể hơi khác một chút.

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. Tóm tắt

Kotlin cung cấp chức năng giúp bạn quản lý và thao tác với tập hợp dữ liệu một cách dễ dàng hơn thông qua Thư viện chuẩn Kotlin. Tập hợp (collecction) có thể được định nghĩa là một số đối tượng thuộc cùng một loại dữ liệu. Kotlin có các loại tập hợp cơ bản gồm: danh sách (list), tập hợp phần tử (set) và sơ đồ ánh xạ (map). Lớp học lập trình này tập trung cụ thể vào danh sách. Bạn cũng sẽ tìm hiểu thêm về tập hợp phần tử và sơ đồ ánh xạ trong các lớp học lập trình sau này.

  • Danh sách là một tập hợp các phần tử thuộc một loại cụ thể, chẳng hạn như danh sách Strings.
  • Chỉ mục là vị trí số nguyên phản ánh vị trí của phần tử (ví dụ: myList[2]).
  • Trong danh sách, phần tử đầu tiên nằm ở chỉ mục 0 (ví dụ: myList[0]) và phần tử cuối cùng nằm tại myList.size-1 (ví dụ: myList[myList.size-1] hoặc myList.last()).
  • Có hai loại danh sách: ListMutableList.
  • List là danh sách chỉ cho phép đọc và không thể chỉnh sửa sau khi khởi tạo. Tuy nhiên, bạn có thể áp dụng các thao tác như sorted()reversed(). Các thao tác này sẽ trả về danh sách mới mà không thay đổi danh sách gốc.
  • Bạn có thể chỉnh sửa MutableList sau khi tạo, chẳng hạn như thêm, xoá hoặc sửa đổi các phần tử.
  • Bạn có thể thêm danh sách các mục vào một danh sách có thể thay đổi bằng cách sử dụng addAll().
  • Sử dụng vòng lặp while để thực thi khối mã cho đến khi biểu thức có giá trị false và bạn thoát khỏi vòng lặp.

while (expression) {

// While the expression is true, execute this code block

}

  • Sử dụng vòng lặp for để lặp lại thao tác cho tất cả các mục của danh sách:

for (item in myList) {

// Execute this code block for each element of the list

}

  • Biến bổ trợ vararg cho phép bạn truyền nhiều đối số cùng loại vào một hàm hoặc hàm khởi tạo, số lượng đối số có thể thay đổi.

9. Tìm hiểu thêm