Lớp học lập trình này nằm trong khoá học Chương trình đào tạo về Kotlin dành cho lập trình viên. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tuỳ thuộc vào kiến thức của mình, bạn có thể lướt qua một số phần. Khoá học này hướng tới những lập trình viên biết một ngôn ngữ hướng đối tượng và muốn học Kotlin.
Giới thiệu
Trong lớp học lập trình này, bạn sẽ được giới thiệu một số tính năng hữu ích khác nhau trong Kotlin, bao gồm các cặp, bộ sưu tập và hàm mở rộng.
Thay vì tạo một ứng dụng mẫu duy nhất, các bài học trong khoá học này được thiết kế để giúp bạn nâng cao kiến thức, nhưng các bài học này có tính độc lập tương đối với nhau để bạn có thể lướt qua những phần mà bạn đã quen thuộc. Để liên kết các ví dụ này với nhau, nhiều ví dụ sử dụng chủ đề hồ cá. Nếu bạn muốn xem toàn bộ câu chuyện về bể cá, hãy tham khảo khoá học Kotlin Bootcamp dành cho lập trình viên của Udacity.
Kiến thức bạn cần có
- Cú pháp của các hàm, lớp và phương thức Kotlin
- Cách xử lý REPL (Vòng lặp Read-Eval-Print) của Kotlin trong IntelliJ IDEA
- Cách tạo một lớp mới trong IntelliJ IDEA và chạy một chương trình
Kiến thức bạn sẽ học được
- Cách làm việc với các cặp và bộ ba
- Thông tin khác về bộ sưu tập
- Xác định và sử dụng hằng số
- Viết hàm mở rộng
Bạn sẽ thực hiện
- Tìm hiểu về các cặp, bộ ba và bản đồ băm trong REPL
- Tìm hiểu các cách sắp xếp hằng số
- Viết một hàm mở rộng và một thuộc tính mở rộng
Trong nhiệm vụ này, bạn sẽ tìm hiểu về cặp và bộ ba cũng như cách phân tách chúng. Cặp và bộ ba là các lớp dữ liệu được tạo sẵn cho 2 hoặc 3 mục chung. Ví dụ: điều này có thể hữu ích khi bạn muốn một hàm trả về nhiều giá trị.
Giả sử bạn có một List
cá và một hàm isFreshWater()
để kiểm tra xem cá đó là cá nước ngọt hay cá nước mặn. List.partition()
trả về 2 danh sách, một danh sách có các mục mà điều kiện là true
và danh sách còn lại có các mục mà điều kiện là false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
Bước 1: Tạo một số cặp và bộ ba
- Mở REPL (Tools > Kotlin > Kotlin REPL).
- Tạo một cặp, liên kết một thiết bị với mục đích sử dụng, sau đó in các giá trị. Bạn có thể tạo một cặp bằng cách tạo một biểu thức kết nối hai giá trị (chẳng hạn như hai chuỗi) bằng từ khoá
to
, sau đó sử dụng.first
hoặc.second
để tham chiếu đến từng giá trị.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- Tạo một bộ ba và in bộ ba đó bằng
toString()
, sau đó chuyển đổi bộ ba đó thành một danh sách bằngtoList()
. Bạn tạo một bộ ba bằng cách sử dụngTriple()
với 3 giá trị. Sử dụng.first
,.second
và.third
để tham chiếu đến từng giá trị.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42) [6, 9, 42]
Các ví dụ trên sử dụng cùng một loại cho tất cả các phần của cặp hoặc bộ ba, nhưng điều đó không bắt buộc. Các phần có thể là một chuỗi, một số hoặc một danh sách, chẳng hạn như một cặp hoặc bộ ba khác.
- Tạo một cặp trong đó phần đầu tiên của cặp đó là một cặp.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment ⇒ catching fish
Bước 2: Phân tách một số cặp và bộ ba
Việc tách các cặp và bộ ba thành các phần được gọi là phân tách cấu trúc. Chỉ định cặp hoặc bộ ba cho số lượng biến thích hợp và Kotlin sẽ chỉ định giá trị của từng phần theo thứ tự.
- Phân tách một cặp và in các giá trị.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
- Phân tách một bộ ba và in các giá trị.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42
Xin lưu ý rằng việc phân tách các cặp và bộ ba hoạt động giống như với các lớp dữ liệu, như đã đề cập trong một lớp học lập trình trước đây.
Trong nhiệm vụ này, bạn sẽ tìm hiểu thêm về các tập hợp, bao gồm cả danh sách và một loại tập hợp mới là bảng băm.
Bước 1: Tìm hiểu thêm về danh sách
- Danh sách và danh sách có thể thay đổi đã được giới thiệu trong một bài học trước đó. Đây là một cấu trúc dữ liệu rất hữu ích, vì vậy Kotlin cung cấp một số hàm tích hợp cho danh sách. Xem danh sách hàm một phần này để biết danh sách. Bạn có thể tìm thấy danh sách đầy đủ trong tài liệu về Kotlin cho
List
vàMutableList
.
Chức năng | Mục đích |
| Thêm một mục vào danh sách có thể thay đổi. |
| Xoá một mục khỏi danh sách có thể thay đổi. |
| Trả về bản sao của danh sách có các phần tử theo thứ tự đảo ngược. |
| Trả về |
| Trả về một phần của danh sách, từ chỉ mục đầu tiên cho đến trước chỉ mục thứ hai. |
- Vẫn đang làm việc trong REPL, hãy tạo một danh sách các số và gọi
sum()
trên danh sách đó. Thao tác này sẽ tổng hợp tất cả các phần tử.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Tạo một danh sách các chuỗi và tính tổng danh sách đó.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
- Nếu phần tử không phải là thứ mà
List
biết cách tính tổng trực tiếp, chẳng hạn như một chuỗi, bạn có thể chỉ định cách tính tổng bằng cách sử dụng.sumBy()
với một hàm lambda, ví dụ: để tính tổng theo độ dài của từng chuỗi. Tên mặc định cho đối số lambda làit
và ở đâyit
đề cập đến từng phần tử của danh sách khi danh sách được duyệt qua.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Bạn có thể làm nhiều việc hơn nữa với danh sách. Một cách để xem các chức năng có sẵn là tạo một danh sách trong IntelliJ IDEA, thêm dấu chấm rồi xem danh sách tự động hoàn thành trong chú thích. Điều này áp dụng cho mọi đối tượng. Hãy thử với một danh sách.
- Chọn
listIterator()
trong danh sách, sau đó duyệt qua danh sách bằng câu lệnhfor
và in tất cả các phần tử được phân tách bằng dấu cách.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
println("$s ")
}
⇒ a bbb cc
Bước 2: Thử dùng bản đồ băm
Trong Kotlin, bạn có thể ánh xạ hầu hết mọi thứ với mọi thứ khác bằng cách sử dụng hashMapOf()
. Bảng băm giống như một danh sách các cặp, trong đó giá trị đầu tiên đóng vai trò là khoá.
- Tạo một bảng băm khớp với các triệu chứng (khoá) và bệnh của cá (giá trị).
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Sau đó, bạn có thể truy xuất giá trị bệnh dựa trên khoá triệu chứng bằng cách sử dụng
get()
hoặc thậm chí là dấu ngoặc vuông[]
.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- Hãy thử chỉ định một triệu chứng không có trong bản đồ.
println(cures["scale loss"])
⇒ null
Nếu một khoá không có trong bản đồ, việc cố gắng trả về bệnh trùng khớp sẽ trả về null
. Tuỳ thuộc vào dữ liệu bản đồ, có thể thường không có kết quả khớp cho một khoá có thể có. Đối với những trường hợp như vậy, Kotlin cung cấp hàm getOrDefault()
.
- Thử tìm một khoá không khớp bằng cách sử dụng
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
Nếu bạn cần làm nhiều việc hơn là chỉ trả về một giá trị, Kotlin cung cấp hàm getOrElse()
.
- Thay đổi mã để sử dụng
getOrElse()
thay vìgetOrDefault()
.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this
Thay vì trả về một giá trị mặc định đơn giản, mọi mã nằm giữa dấu ngoặc nhọn {}
đều được thực thi. Trong ví dụ này, else
chỉ trả về một chuỗi, nhưng có thể phức tạp như tìm một trang web có phương pháp chữa trị và trả về trang web đó.
Giống như mutableListOf
, bạn cũng có thể tạo mutableMapOf
. Bản đồ có thể thay đổi cho phép bạn đặt và xoá các mục. Có thể thay đổi có nghĩa là có thể thay đổi, không thể thay đổi có nghĩa là không thể thay đổi.
- Tạo một bản đồ kho hàng có thể sửa đổi, ánh xạ một chuỗi thiết bị với số lượng mặt hàng. Tạo một bể cá có lưới đánh cá, sau đó thêm 3 miếng cọ rửa bể cá vào kho hàng bằng
put()
và loại bỏ lưới đánh cá bằngremove()
.
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}
Trong tác vụ này, bạn sẽ tìm hiểu về hằng số trong Kotlin và các cách sắp xếp hằng số.
Bước 1: Tìm hiểu về const và val
- Trong REPL, hãy thử tạo một hằng số dạng số. Trong Kotlin, bạn có thể tạo các hằng số cấp cao nhất và chỉ định giá trị cho các hằng số đó tại thời gian biên dịch bằng cách sử dụng
const val
.
const val rocks = 3
Giá trị được chỉ định và không thể thay đổi, điều này nghe có vẻ giống như việc khai báo một val
thông thường. Vậy có gì khác biệt giữa const val
và val
? Giá trị cho const val
được xác định tại thời gian biên dịch, trong khi giá trị cho val
được xác định trong quá trình thực thi chương trình. Điều này có nghĩa là val
có thể được chỉ định bằng một hàm tại thời gian chạy.
Điều đó có nghĩa là val
có thể được gán một giá trị từ một hàm, nhưng const val
thì không.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
Ngoài ra, const val
chỉ hoạt động ở cấp cao nhất và trong các lớp singleton được khai báo bằng object
, chứ không hoạt động với các lớp thông thường. Bạn có thể dùng cách này để tạo một tệp hoặc đối tượng singleton chỉ chứa các hằng số và nhập các hằng số đó khi cần.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
Bước 2: Tạo một đối tượng đi kèm
Kotlin không có khái niệm về hằng số cấp lớp.
Để xác định hằng số bên trong một lớp, bạn phải gói các hằng số đó vào các đối tượng đồng hành được khai báo bằng từ khoá companion
. Đối tượng đồng hành về cơ bản là một đối tượng singleton trong lớp.
- Tạo một lớp có đối tượng đồng hành chứa hằng số chuỗi.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
Sự khác biệt cơ bản giữa các đối tượng đi kèm và đối tượng thông thường là:
- Các đối tượng đồng hành được khởi tạo từ hàm khởi tạo tĩnh của lớp chứa, tức là chúng được tạo khi đối tượng được tạo.
- Các đối tượng thông thường được khởi tạo từng phần trong lần truy cập đầu tiên vào đối tượng đó; tức là khi chúng được sử dụng lần đầu tiên.
Còn nhiều điều khác nữa, nhưng hiện tại bạn chỉ cần biết cách bao bọc các hằng số trong các lớp trong một đối tượng companion.
Trong nhiệm vụ này, bạn sẽ tìm hiểu về cách mở rộng hành vi của các lớp. Việc viết các hàm tiện ích để mở rộng hành vi của một lớp là điều rất phổ biến. Kotlin cung cấp một cú pháp thuận tiện để khai báo các hàm tiện ích này: hàm mở rộng.
Hàm mở rộng cho phép bạn thêm hàm vào một lớp hiện có mà không phải truy cập vào mã nguồn của lớp đó. Ví dụ: bạn có thể khai báo chúng trong tệp Extensions.kt thuộc gói của mình. Thao tác này không thực sự sửa đổi lớp, nhưng cho phép bạn sử dụng ký hiệu dấu chấm khi gọi hàm trên các đối tượng của lớp đó.
Bước 1: Viết một hàm mở rộng
- Vẫn làm việc trong REPL, hãy viết một hàm mở rộng đơn giản,
hasSpaces()
để kiểm tra xem một chuỗi có chứa khoảng trắng hay không. Tên hàm bắt đầu bằng lớp mà tại đó hàm này hoạt động. Bên trong hàm,this
đề cập đến đối tượng mà hàm được gọi vàit
đề cập đến trình lặp trong lệnh gọifind()
.
fun String.hasSpaces(): Boolean {
val found = this.find { it == ' ' }
return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
- Bạn có thể đơn giản hoá hàm
hasSpaces()
. Bạn không cần phải chỉ định rõ ràngthis
và hàm có thể được rút gọn thành một biểu thức duy nhất và được trả về, do đó, bạn cũng không cần dấu ngoặc nhọn{}
xung quanh hàm.
fun String.hasSpaces() = find { it == ' ' } != null
Bước 2: Tìm hiểu các giới hạn của tiện ích
Các hàm mở rộng chỉ có quyền truy cập vào API công khai của lớp mà chúng đang mở rộng. Bạn không thể truy cập vào các biến private
.
- Hãy thử thêm các hàm mở rộng vào một thuộc tính được đánh dấu là
private
.
class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red" // OK
fun AquariumPlant.isBig() = size > 50 // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
- Hãy xem xét mã bên dưới và tìm hiểu xem mã đó sẽ in nội dung gì.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print() // what will it print?
⇒ GreenLeafyPlant AquariumPlant
plant.print()
ảnh in GreenLeafyPlant
. Bạn cũng có thể mong đợi aquariumPlant.print()
in GreenLeafyPlant
vì nó được gán giá trị của plant
. Nhưng loại này được phân giải tại thời điểm biên dịch, nên AquariumPlant
sẽ được in.
Bước 3: Thêm một thuộc tính tiện ích
Ngoài các hàm mở rộng, Kotlin cũng cho phép bạn thêm các thuộc tính mở rộng. Giống như hàm mở rộng, bạn chỉ định lớp mà bạn đang mở rộng, theo sau là dấu chấm, theo sau là tên thuộc tính.
- Vẫn đang làm việc trong REPL, hãy thêm một thuộc tính mở rộng
isGreen
vàoAquariumPlant
, làtrue
nếu màu là xanh lục.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
Bạn có thể truy cập vào thuộc tính isGreen
giống như một thuộc tính thông thường; khi được truy cập, phương thức getter cho isGreen
sẽ được gọi để lấy giá trị.
- In thuộc tính
isGreen
cho biếnaquariumPlant
và quan sát kết quả.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Bước 4: Tìm hiểu về các receiver có thể rỗng
Lớp mà bạn mở rộng được gọi là receiver và bạn có thể đặt lớp đó là có giá trị rỗng. Nếu bạn làm như vậy, biến this
được dùng trong phần nội dung có thể là null
, vì vậy, hãy nhớ kiểm thử để đảm bảo điều đó. Bạn nên dùng một receiver có thể rỗng nếu dự kiến người gọi sẽ muốn gọi phương thức mở rộng của bạn trên các biến có thể rỗng, hoặc nếu bạn muốn cung cấp một hành vi mặc định khi hàm của bạn được áp dụng cho null
.
- Vẫn làm việc trong REPL, hãy xác định một phương thức
pull()
lấy một đối tượng nhận có thể rỗng. Điều này được biểu thị bằng dấu chấm hỏi?
sau loại, trước dấu chấm. Trong phần nội dung, bạn có thể kiểm thử xemthis
có phải lànull
hay không bằng cách sử dụng questionmark-dot-apply?.apply.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- Trong trường hợp này, sẽ không có kết quả đầu ra khi bạn chạy chương trình. Vì
plant
lànull
, nênprintln()
bên trong không được gọi.
Các hàm mở rộng rất hữu hiệu và hầu hết thư viện chuẩn Kotlin được triển khai dưới dạng các hàm mở rộng.
Trong bài học này, bạn đã tìm hiểu thêm về các tập hợp, hằng số và biết được sức mạnh của các hàm và thuộc tính mở rộng.
- Bạn có thể dùng cặp và bộ ba để trả về nhiều giá trị từ một hàm. Ví dụ:
val twoLists = fish.partition { isFreshWater(it) }
- Kotlin có nhiều hàm hữu ích cho
List
, chẳng hạn nhưreversed()
,contains()
vàsubList()
. - Bạn có thể dùng
HashMap
để liên kết các khoá với các giá trị. Ví dụ:val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Khai báo hằng số thời gian biên dịch bằng cách dùng từ khoá
const
. Bạn có thể đặt chúng ở cấp cao nhất, sắp xếp chúng trong một đối tượng singleton hoặc đặt chúng trong một đối tượng đồng hành. - Đối tượng đồng hành là một đối tượng singleton trong định nghĩa lớp, được xác định bằng từ khoá
companion
. - Các hàm và thuộc tính mở rộng có thể thêm chức năng cho một lớp. Ví dụ:
fun String.hasSpaces() = find { it == ' ' } != null
- Đối tượng nhận có thể rỗng cho phép bạn tạo các tiện ích trên một lớp có thể là
null
. Bạn có thể ghép toán tử?.
vớiapply
để kiểm tranull
trước khi thực thi mã. Ví dụ:this?.apply { println("removing $this") }
Tài liệu về Kotlin
Nếu bạn muốn biết thêm thông tin về bất kỳ chủ đề nào trong khoá học này hoặc nếu bạn gặp khó khăn, thì https://kotlinlang.org là nơi tốt nhất để bạn bắt đầu.
Hướng dẫn về Kotlin
Trang web https://try.kotlinlang.org có các hướng dẫn phong phú được gọi là Kotlin Koans, một trình thông dịch dựa trên web và một bộ tài liệu tham khảo đầy đủ kèm theo ví dụ.
Khoá học của Udacity
Để xem khoá học của Udacity về chủ đề này, hãy xem Chương trình đào tạo về Kotlin dành cho lập trình viên.
IntelliJ IDEA
Bạn có thể xem tài liệu về IntelliJ IDEA trên trang web của JetBrains.
Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:
- Giao bài tập về nhà nếu cần.
- Trao đổi với học viên về cách nộp bài tập về nhà.
- Chấm điểm bài tập về nhà.
Người hướng dẫn có thể sử dụng các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.
Nếu bạn đang tự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.
Trả lời các câu hỏi sau
Câu hỏi 1
Hàm nào sau đây trả về bản sao của một danh sách?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Câu hỏi 2
Hàm mở rộng nào trong số này trên class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
sẽ gây ra lỗi trình biên dịch?
▢ fun AquariumPlant.isRed() = color == "red"
▢ fun AquariumPlant.isBig() = size > 45
▢ fun AquariumPlant.isExpensive() = cost > 10.00
▢ fun AquariumPlant.isNotLeafy() = leafy == false
Câu hỏi 3
Bạn không thể xác định hằng số bằng const val
ở nơi nào sau đây?
▢ ở cấp cao nhất của một tệp
▢ trong các lớp học thông thường
▢ trong các đối tượng singleton
▢ trong các đối tượng đồng hành
Chuyển sang bài học tiếp theo:
Để biết thông tin tổng quan về khoá học này, bao gồm cả đường liên kết đến các lớp học lập trình khác, hãy xem "Chương trình đào tạo về Kotlin dành cho lập trình viên: Chào mừng bạn đến với khoá học".