Thực hành: Kiến thức cơ bản về Kotlin

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

Bạn đã bỏ nhiều công sức để tìm hiểu những kiến thức cơ bản về lập trình Kotlin, đã đến lúc áp dụng những gì bạn được học vào thực tế.

Các bài tập này kiểm tra mức độ hiểu biết của bạn về những khái niệm đã học. Nó được xây dựng dựa vào các tình huống trên thực tế, một trong số đó trước đây bạn có thể đã gặp với tư cách người dùng.

Làm theo hướng dẫn để tìm giải pháp cho từng bài tập trong Kotlin Playground. Nếu gặp khó khăn, một số bài tập sẽ cung cấp gợi ý có thể trợ giúp bạn. Mã giải pháp cho mỗi bài tập sẽ có sẵn ở phần cuối, nhưng bạn nên tìm ra cách giải các bài tập trước khi kiểm tra câu trả lời.

Làm bài tập với tốc độ bạn cảm thấy thoải mái. Có thời gian hoàn thành ước tính cho các bài tập, nhưng đó chỉ là ước tính, vì vậy bạn không nhất thiết phải tuân thủ theo đó. Hãy dành nhiều thời gian cần thiết nhất có thể để giải quyết vấn đề một cách hợp lý. Những đáp án đó chỉ là một cách để giải các bài tập, vì vậy, hãy tự do thử nghiệm theo cách bạn cảm thấy thoải mái.

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

Bạn cần có

  • Kotlin Playground

2. Thông báo qua thiết bị di động

Thông thường, điện thoại sẽ báo cho bạn một bản tóm tắt của thông báo.

Ở mã ban đầu được cung cấp trong đoạn mã sau, hãy viết một chương trình in tin nhắn tóm tắt dựa trên số lượng thông báo bạn đã nhận được. Tin nhắn phải bao gồm:

  • Số lượng thông báo chính xác khi có ít hơn 100 thông báo.
  • 99+ làm số lượng thông báo khi có từ 100 thông báo trở lên.
fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    // Fill in the code.
}

Hãy hoàn tất hàm printNotificationSummary() để lập trình in những dòng sau:

You have 51 notifications.
Your phone is blowing up! You have 99+ notifications.

3. Giá vé xem phim

Vé xem phim thường có giá khác nhau tuỳ theo độ tuổi của người xem.

Ở mã ban đầu được cung cấp trong đoạn mã sau đây, hãy viết một lập trình tính giá vé dựa theo độ tuổi sau:

  • Giá vé dành cho trẻ em là $15 cho người dưới 12 tuổi.
  • Giá vé tiêu chuẩn là $30 cho người từ 13 đến 60 tuổi. Thứ Hai, giảm giá vé tiêu chuẩn xuống còn $25 cho cùng nhóm tuổi này.
  • Giá vé người cao tuổi là $20 dành cho người từ 61 tuổi trở lên. Giả sử độ tuổi tối đa của người xem phim là 100 tuổi.
  • Giá trị -1 để cho biết giá không hợp lệ khi người dùng nhập một độ tuổi bên ngoài quy định về độ tuổi.
fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    // Fill in the code.
}

Hãy hoàn tất hàm ticketPrice() để lập trình in những dòng sau:

The movie ticket price for a person aged 5 is $15.
The movie ticket price for a person aged 28 is $25.
The movie ticket price for a person aged 87 is $20.

4. Bộ chuyển đổi nhiệt độ

Có 3 thang nhiệt chính trên thế giới: độ C, độ F và độ K.

Ở mã ban đầu được cung cấp trong đoạn mã sau đây, hãy viết một lập trình chuyển đổi nhiệt độ từ thang nhiệt độ này sang thang khác với các công thức sau:

  • Độ C chuyển sang độ F: °F = 9/5 (°C) + 32
  • Độ K chuyển sang độ C: ° C = K - 273,15
  • Độ F chuyển sang độ K: K = 5/9 (° F – 32) + 273,15

Lưu ý phương thức String.format("%.2f", /* measurement */ ) được dùng để chuyển đổi một số thành loại String có 2 chữ số thập phân.

fun main() {
    // Fill in the code.
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

Hãy hoàn tất hàm main() để gọi hàm printFinalTemperature() và in các dòng sau. Bạn cần truyền các đối số cho công thức chuyển đổi và nhiệt độ. Gợi ý: bạn nên sử dụng các giá trị Double để tránh bị cắt bớt Integer trong các phép chia.

27.0 degrees Celsius is 80.60 degrees Fahrenheit.
350.0 degrees Kelvin is 76.85 degrees Celsius.
10.0 degrees Fahrenheit is 260.93 degrees Kelvin.

5. Danh mục bài hát

Thử tưởng tượng bạn cần tạo một ứng dụng trình phát nhạc.

Tạo một lớp có thể đại diện cho cấu trúc của một bài hát. Lớp Song phải bao gồm các thành phần mã sau:

  • Thuộc tính cho tiêu đề, nghệ sĩ, năm xuất bản và số lần phát
  • Thuộc tính cho biết bài hát có phổ biến hay không. Nếu số lượt phát thấp hơn 1.000 lần, hãy coi bài hát đó không phổ biến.
  • Phương thức in nội dung mô tả bài hát theo định dạng sau:

"[Tiêu đề], do [nghệ sĩ] thực hiện, được phát hành vào [năm phát hành]".

6. Hồ sơ trên Internet

Thông thường, bạn được yêu cầu hoàn tất hồ sơ trên các trang web trực tuyến có chứa các trường bắt buộc và không bắt buộc. Ví dụ: bạn có thể thêm thông tin cá nhân của mình và liên kết đến những người khác đã giới thiệu bạn đăng ký hồ sơ này.

Ở mã ban đầu được cung cấp trong đoạn mã sau, hãy viết một lập trình in ra thông tin chi tiết về hồ sơ của một người.

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
       // Fill in code
    }
}

Hãy hoàn tất hàm showProfile() để lập trình in những dòng sau:

Name: Amanda
Age: 33
Likes to play tennis. Doesn't have a referrer.

Name: Atiqah
Age: 28
Likes to climb. Has a referrer named Amanda, who likes to play tennis.

7. Điện thoại có thể gập lại

Thông thường, màn hình điện thoại sẽ bật và tắt khi được nhấn nút nguồn. Ngược lại, đối với điện thoại có thể gập, màn hình chính bên trong trên điện thoại có thể gập lại sẽ không bật khi người dùng nhấn nút nguồn.

Ở mã ban đầu được cung cấp trong đoạn mã sau, hãy viết một lớp FoldablePhone kế thừa từ lớp Phone. Nó phải chứa các thông tin sau:

  • Một thuộc tính cho biết liệu điện thoại có gập được hay không.
  • Một hàm switchOn() có hành vi khác với lớp Phone để điện thoại chỉ bật khi màn hình không được gập.
  • Các phương thức thay đổi trạng thái gập.
class Phone(var isScreenLightOn: Boolean = false){
    fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if (isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

8. Phiên đấu giá đặc biệt

Thông thường trong phiên đấu giá, người đặt giá thầu cao nhất sẽ xác định giá của một mặt hàng. Trong phiên đấu giá đặc biệt này, nếu không có người đặt giá thầu cho một mặt hàng, thì mặt hàng đó sẽ tự động được bán cho nhà tổ chức đấu giá ở mức giá tối thiểu.

Ở mã ban đầu được cung cấp trong đoạn mã sau, bạn được cung cấp hàm auctionPrice() chấp nhận loại Bid? có tính rỗng như một đối số:

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
   // Fill in the code.
}

Hãy hoàn tất hàm auctionPrice() để lập trình in những dòng sau:

Item A is sold at 5000.
Item B is sold at 3000.

9. Mã giải pháp

Thông báo qua thiết bị di động

Giải pháp này sử dụng một câu lệnh if/else để in tin nhắn tóm tắt thông báo thích hợp dựa trên số lượng thông báo nhận được:

fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    if (numberOfMessages < 100) {
        println("You have ${numberOfMessages} notifications.")
    } else {
        println("Your phone is blowing up! You have 99+ notifications.")
    }
}

Giá vé xem phim

Giải pháp sử dụng biểu thức when để trả về giá vé phù hợp dựa trên độ tuổi của người xem phim. Nó cũng sử dụng một biểu thức if/else đơn giản cho một trong các nhánh của biểu thức when để thêm điều kiện bổ sung cho giá vé tiêu chuẩn.

Giá vé tại nhánh else trả về một giá trị -1, cho biết giá vé thiết lập không hợp lệ đối với nhánh else. Cách tốt hơn là cho nhánh else một ngoại lệ. Bạn sẽ tìm hiểu về cách xử lý ngoại lệ trong các bài sau.

fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    return when(age) {
        in 0..12 -> 15
        in 13..60 -> if (isMonday) 25 else 30
        in 61..100 -> 20
        else -> -1
    }
}

Bộ chuyển đổi nhiệt độ

Giải pháp này yêu cầu bạn chuyển một hàm ở dạng tham số vào hàm printFinalTemperature(). Giải pháp ngắn gọn nhất chuyển các biểu thức lambda làm đối số, sử dụng tham chiếu it thay cho tên tham số và sử dụng cú pháp lambda tạo vệt.

fun main() {
        printFinalTemperature(27.0, "Celsius", "Fahrenheit") { 9.0 / 5.0 * it + 32 }
        printFinalTemperature(350.0, "Kelvin", "Celsius") { it - 273.15 }
        printFinalTemperature(10.0, "Fahrenheit", "Kelvin") { 5.0 / 9.0 * (it - 32) + 273.15 }
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

Danh mục bài hát

Giải pháp này chứa một lớp Song có một hàm dựng mặc định chấp nhận mọi thông số bắt buộc. Lớp Song cũng có một thuộc tính isPopular sử dụng hàm phương thức getter tùy chỉnh và một phương thức in nội dung mô tả về chính phương thức đó. Bạn có thể tạo một thực thể của lớp trong hàm main() và gọi các phương thức của lớp đó để kiểm tra xem cách triển khai đó có chính xác hay không. Bạn có thể dùng dấu gạch dưới khi viết các số lớn như giá trị 1_000_000 để dễ đọc hơn.

fun main() {
    val brunoSong = Song("We Don't Talk About Bruno", "Encanto Cast", 2022, 1_000_000)
    brunoSong.printDescription()
    println(brunoSong.isPopular)
}

class Song(
    val title: String,
    val artist: String,
    val yearPublished: Int,
    val playCount: Int
){
    val isPopular: Boolean
        get() = playCount >= 1000

    fun printDescription() {
        println("$title, performed by $artist, was released in $yearPublished.")
    }
}

Khi bạn gọi hàm println() trên các phương thức của thực thể, lập trình có thể in kết quả như sau:

We Don't Talk About Bruno, performed by Encanto Cast, was released in 2022.
true

Hồ sơ trên Internet

Giải pháp này chứa các giá trị kiểm tra biến rỗng trong nhiều câu lệnh if/else để in văn bản khác nhau dựa trên việc các thuộc tính lớp khác nhau có phải là null hay không:

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
        println("Name: $name")
        println("Age: $age")
        if(hobby != null) {
            print("Likes to $hobby. ")
        }
        if(referrer != null) {
            print("Has a referrer named ${referrer.name}")
            if(referrer.hobby != null) {
                print(", who likes to ${referrer.hobby}.")
            } else {
                print(".")
            }
        } else {
            print("Doesn't have a referrer.")
        }
        print("\n\n")
    }
}

Điện thoại có thể gập lại

Để Phone là lớp mẹ, bạn cần mở lớp này bằng cách thêm từ khoá open vào trước tên lớp. Để ghi đè phương thức switchOn() trong lớp FoldablePhone, bạn cần đặt phương thức trong lớp Phone mở bằng cách thêm từ khóa open trước phương thức.

Giải pháp này chứa một lớp FoldablePhone có một hàm dựng mặc định chứa đối số mặc định cho tham số isFolded. Lớp FoldablePhone cũng có hai phương thức để thay đổi thuộc tính isFolded thành giá trị true hoặc false. Nó này cũng ghi đè phương thức switchOn() được kế thừa từ lớp Phone.

Bạn có thể tạo một thực thể của lớp trong hàm main() và gọi các phương thức của lớp đó để kiểm tra xem cách triển khai đó có chính xác hay không.

open class Phone(var isScreenLightOn: Boolean = false){
    open fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if (isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

class FoldablePhone(var isFolded: Boolean = true): Phone() {
    override fun switchOn() {
        if (!isFolded) {
            isScreenLightOn = true
        }
    }

    fun fold() {
        isFolded = true
    }

    fun unfold() {
        isFolded = false
    }
}

fun main() {
    val newFoldablePhone = FoldablePhone()

    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
    newFoldablePhone.unfold()
    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
}

Kết quả sẽ như sau:

The phone screen's light is off.
The phone screen's light is on.

Phiên đấu giá đặc biệt

Giải pháp này sử dụng toán tử an toàn cho lệnh gọi ?. và toán tử Elvis ?: để trả về đúng giá:

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
    return bid?.amount ?: minimumPrice
}

10. Thực hành bổ sung

Để thực hành thêm về ngôn ngữ Kotlin, hãy xem kênh Kotlin Basics (Kiến thức cơ bản về Kotlin) tại JetBrains Academy. Để tìm chính xác một chủ đề cụ thể, hãy chuyển đến bản đồ tri thức để xem danh sách các chủ đề được đề cập trong kênh.