Chương trình đào tạo về Kotlin dành cho lập trình viên 3: Hàm

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ẽ tạo một chương trình Kotlin và tìm hiểu về các hàm trong Kotlin, bao gồm cả các giá trị mặc định cho tham số, bộ lọc, hàm lambda và các hàm thu gọn.

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ó

  • Kiến thức cơ bản về một ngôn ngữ lập trình hiện đại, hướng đối tượng, kiểu tĩnh
  • Cách lập trình bằng các lớp, phương thức và xử lý ngoại lệ bằng ít nhất một ngôn ngữ
  • Cách xử lý REPL (Vòng lặp Read-Eval-Print) của Kotlin trong IntelliJ IDEA
  • Kiến thức cơ bản về Kotlin, bao gồm cả các loại, toán tử và vòng lặp

Lớp học lập trình này dành cho những lập trình viên biết một ngôn ngữ hướng đối tượng và muốn tìm hiểu thêm về Kotlin.

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

  • Cách tạo chương trình bằng hàm main() và các đối số trong IntelliJ IDEA
  • Cách sử dụng các giá trị mặc định và hàm thu gọn
  • Cách áp dụng bộ lọc cho danh sách
  • Cách tạo hàm lambda cơ bản và hàm bậc cao

Bạn sẽ thực hiện

  • Làm việc với REPL để thử một số mã.
  • Làm việc với IntelliJ IDEA để tạo các chương trình Kotlin cơ bản.

Trong nhiệm vụ này, bạn sẽ tạo một chương trình Kotlin và tìm hiểu về hàm main(), cũng như cách truyền đối số cho một chương trình từ dòng lệnh.

Bạn có thể nhớ hàm printHello() mà bạn đã nhập vào REPL trong một lớp học lập trình trước đó:

fun printHello() {
    println ("Hello World")
}

printHello()
⇒ Hello World

Bạn xác định các hàm bằng từ khoá fun theo sau là tên hàm. Cũng giống như các ngôn ngữ lập trình khác, dấu ngoặc đơn () là dành cho đối số của hàm (nếu có). Dấu ngoặc nhọn {} dùng để tạo khung mã cho hàm. Không có loại dữ liệu trả về cho hàm này vì hàm không trả về dữ liệu nào.

Bước 1: Tạo một tệp Kotlin

  1. Mở IntelliJ IDEA.
  2. Ngăn Project (Dự án) ở bên trái trong IntelliJ IDEA cho thấy danh sách các tệp và thư mục của dự án. Tìm và nhấp chuột phải vào thư mục src trong Hello Kotlin. (Bạn đã có dự án Hello Kotlin trong lớp học lập trình trước.)
  3. Chọn New > Kotlin File / Class (Mới > Tệp/Lớp Kotlin).
  4. Giữ Kind (Loại) là File (Tệp) và đặt tên cho tệp là Hello.
  5. Nhấp vào OK.

Hiện có một tệp trong thư mục src có tên là Hello.kt.

Bước 2: Thêm mã và chạy chương trình

  1. Cũng giống như các ngôn ngữ khác, hàm main() trong Kotlin chỉ định điểm bắt đầu để thực thi. Mọi đối số của dòng lệnh đều được chuyển ở dạng mảng chuỗi.

    Nhập hoặc dán mã sau vào tệp Hello.kt :
fun main(args: Array<String>) {
    println("Hello, world!")
}

Giống như hàm printHello() trước đó, hàm này không có câu lệnh return. Mọi hàm trong Kotlin đều trả về một nội dung nào đó, ngay cả khi hàm đó không chỉ định rõ giá trị. Vì vậy, một hàm giống như hàm main() này sẽ trả về loại kotlin.Unit, đó là cách Kotlin phản hồi khi không có giá trị.

  1. Để chạy chương trình, hãy nhấp vào hình tam giác màu xanh lục ở bên trái hàm main(). Chọn Run 'HelloKt' (Chạy "HelloKt") trong trình đơn.
  2. IntelliJ IDEA sẽ biên dịch và chạy chương trình. Kết quả sẽ xuất hiện trong một ngăn nhật ký ở dưới cùng, như minh hoạ bên dưới.

Bước 3: Chuyển các đối số cho hàm main()

Vì đang chạy chương trình từ IntelliJ IDEA chứ không phải từ dòng lệnh, nên bạn cần chỉ định các đối số cho chương trình hơi khác một chút.

  1. Chọn Run > Edit Configurations (Chạy > Chỉnh sửa cấu hình). Cửa sổ Run/Debug Configurations (Cấu hình chạy/gỡ lỗi) sẽ mở ra.
  2. Nhập Kotlin! vào trường Program arguments (Đối số chương trình).
  3. Nhấp vào OK.

Bước 4: Thay đổi mã để sử dụng mẫu chuỗi

Mẫu chuỗi chèn một biến hoặc biểu thức vào một chuỗi, còn $ chỉ định rằng một phần của chuỗi sẽ là một biến hoặc biểu thức. Dấu ngoặc nhọn {} đóng khung biểu thức (nếu có).

  1. Trong Hello.kt, hãy thay đổi thông báo chào mừng để dùng đối số đầu tiên được chuyển vào chương trình, args[0], thay vì "world".
fun main(args: Array<String>) {
    println("Hello, ${args[0]}")
}
  1. Chạy chương trình và kết quả sẽ bao gồm đối số mà bạn đã chỉ định.
⇒ Hello, Kotlin!

Trong nhiệm vụ này, bạn sẽ tìm hiểu lý do hầu hết mọi thứ trong Kotlin đều có giá trị và lý do điều đó hữu ích.

Một số ngôn ngữ khác có câu lệnh là các dòng mã không có giá trị. Trong Kotlin, hầu hết mọi thứ đều là một biểu thức và có giá trị – ngay cả khi giá trị đó là kotlin.Unit.

  1. Trong Hello.kt, hãy viết mã trong main() để chỉ định một println() cho một biến có tên là isUnit rồi in biến đó. (println() không trả về giá trị nên hàm này sẽ trả về kotlin.Unit.)
// Will assign kotlin.Unit
val isUnit = println("This is an expression")
println(isUnit)
  1. Chạy chương trình. println() đầu tiên in chuỗi "This is an expression". println() thứ hai in giá trị của câu lệnh println() đầu tiên, tức là kotlin.Unit.
⇒ This is an expression
kotlin.Unit
  1. Khai báo một val có tên là temperature và khởi chạy nó thành 10.
  2. Khai báo một val khác có tên là isHot rồi chỉ định giá trị trả về của câu lệnh if/else cho isHot, như trong đoạn mã sau. Vì đây là một biểu thức, nên bạn có thể sử dụng ngay giá trị của biểu thức if.
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot)
⇒ false
  1. Sử dụng giá trị của một biểu thức trong mẫu chuỗi. Thêm một đoạn mã để kiểm tra nhiệt độ nhằm xác định xem cá có an toàn hay quá nóng, sau đó chạy chương trình.
val temperature = 10
val message = "The water temperature is ${ if (temperature > 50) "too warm" else "OK" }."
println(message)
⇒ The water temperature is OK.

Trong nhiệm vụ này, bạn sẽ tìm hiểu thêm về các hàm trong Kotlin và biểu thức điều kiện when rất hữu ích.

Bước 1: Tạo một số hàm

Trong bước này, bạn sẽ kết hợp một số kiến thức đã học và tạo các hàm có nhiều loại. Bạn có thể thay thế nội dung của Hello.kt bằng mã mới này.

  1. Viết một hàm có tên là feedTheFish() gọi randomDay() để lấy một ngày ngẫu nhiên trong tuần. Sử dụng mẫu chuỗi để in food cho cá ăn vào ngày hôm đó. Hiện tại, cá ăn cùng một loại thức ăn mỗi ngày.
fun feedTheFish() {
    val day = randomDay()
    val food = "pellets"
    println ("Today is $day and the fish eat $food")
}

fun main(args: Array<String>) {
    feedTheFish()
}
  1. Viết hàm randomDay() để chọn một ngày ngẫu nhiên trong mảng và trả về ngày đó.

Hàm nextInt() lấy một giới hạn số nguyên, giới hạn số từ Random() đến 0 thông qua 6 để khớp với mảng week.

fun randomDay() : String {
    val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
            "Friday", "Saturday", "Sunday")
    return week[Random().nextInt(week.size)]
}
  1. Hàm Random()nextInt() được xác định trong java.util.*. Ở đầu tệp, hãy thêm nội dung nhập cần thiết:
import java.util.*    // required import
  1. Chạy chương trình của bạn và kiểm tra kết quả.
⇒ Today is Tuesday and the fish eat pellets

Bước 2: Sử dụng biểu thức when

Để mở rộng thêm, hãy thay đổi mã để chọn các loại thực phẩm khác nhau cho các ngày khác nhau bằng cách sử dụng biểu thức when. Câu lệnh when tương tự như switch trong các ngôn ngữ lập trình khác, nhưng when sẽ tự động ngắt ở cuối mỗi nhánh. Tính năng này cũng đảm bảo mã của bạn bao gồm tất cả các nhánh nếu bạn đang kiểm tra một enum.

  1. Trong Hello.kt, hãy thêm một hàm tên là fishFood(). Hàm này chứa một ngày dưới dạng String và trả về thức ăn của cá trong ngày dưới dạng String. Sử dụng when() để mỗi ngày cá được cho ăn một loại thức ăn cụ thể. Chạy chương trình của bạn vài lần để xem các kết quả khác nhau.
fun fishFood (day : String) : String {
    var food = ""
    when (day) {
        "Monday" -> food = "flakes"
        "Tuesday" -> food = "pellets"
        "Wednesday" -> food = "redworms"
        "Thursday" -> food = "granules"
        "Friday" -> food = "mosquitoes"
        "Saturday" -> food = "lettuce"
        "Sunday" -> food = "plankton"
    }
    return food
}

fun feedTheFish() {
    val day = randomDay()
    val food = fishFood(day)

    println ("Today is $day and the fish eat $food")
}
⇒ Today is Thursday and the fish eat granules
  1. Thêm một nhánh mặc định vào biểu thức when bằng cách sử dụng else. Để kiểm thử, nhằm đảm bảo giá trị mặc định đôi khi được lấy trong chương trình của bạn, hãy xoá các nhánh TuesdaySaturday.

    Việc có một nhánh mặc định đảm bảo rằng food nhận được một giá trị trước khi được trả về, vì vậy, bạn không cần phải khởi tạo nữa. Vì mã hiện chỉ gán một chuỗi cho food một lần, nên bạn có thể khai báo food bằng val thay vì var.
fun fishFood (day : String) : String {
    val food : String
    when (day) {
        "Monday" -> food = "flakes"
        "Wednesday" -> food = "redworms"
        "Thursday" -> food = "granules"
        "Friday" -> food = "mosquitoes"
        "Sunday" -> food = "plankton"
        else -> food = "nothing"
    }
    return food
}
  1. Vì mọi biểu thức đều có giá trị, nên bạn có thể làm cho mã này ngắn gọn hơn một chút. Trả về trực tiếp giá trị của biểu thức when và loại bỏ biến food. Giá trị của biểu thức when là giá trị của biểu thức cuối cùng của nhánh đáp ứng điều kiện.
fun fishFood (day : String) : String {
    return when (day) {
        "Monday" -> "flakes"
        "Wednesday" -> "redworms"
        "Thursday" -> "granules"
        "Friday" -> "mosquitoes"
        "Sunday" -> "plankton"
        else -> "nothing"
    }
}

Phiên bản cuối cùng của chương trình sẽ có dạng như mã bên dưới.

import java.util.*    // required import

fun randomDay() : String {
    val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
        "Friday", "Saturday", "Sunday")
    return week[Random().nextInt(week.size)]
}

fun fishFood (day : String) : String {
    return when (day) {
        "Monday" -> "flakes"
        "Wednesday" -> "redworms"
        "Thursday" -> "granules"
        "Friday" -> "mosquitoes"
        "Sunday" -> "plankton"
        else -> "nothing"
    }
}

fun feedTheFish() {
    val day = randomDay()
    val food = fishFood(day)
    println ("Today is $day and the fish eat $food")
}

fun main(args: Array<String>) {
    feedTheFish()
}

Trong nhiệm vụ này, bạn sẽ tìm hiểu về các giá trị mặc định cho hàm và phương thức. Bạn cũng sẽ tìm hiểu về hàm thu gọn. Hàm này có thể giúp mã của bạn ngắn gọn và dễ đọc hơn, đồng thời giảm số lượng đường dẫn mã để kiểm thử. Hàm thu gọn còn được gọi là hàm một biểu thức.

Bước 1: Tạo giá trị mặc định cho một tham số

Trong Kotlin, bạn có thể truyền các đối số theo tên tham số. Bạn cũng có thể chỉ định giá trị mặc định cho các tham số: nếu người gọi không cung cấp đối số, giá trị mặc định sẽ được sử dụng. Sau này, khi viết các phương thức (hàm thành viên), bạn có thể tránh viết nhiều phiên bản nạp chồng của cùng một phương thức.

  1. Trong Hello.kt, hãy viết một hàm swim() có tham số String được đặt tên là speed để in tốc độ của cá. Tham số speed có giá trị mặc định là "fast".
fun swim(speed: String = "fast") {
   println("swimming $speed")
}
  1. Từ hàm main(), hãy gọi hàm swim() theo 3 cách. Trước tiên, hãy gọi hàm bằng giá trị mặc định. Sau đó, gọi hàm và truyền tham số speed mà không cần tên, rồi gọi hàm bằng cách đặt tên cho tham số speed.
swim()   // uses default speed
swim("slow")   // positional argument
swim(speed="turtle-like")   // named parameter
⇒ swimming fast
swimming slow
swimming turtle-like

Bước 2: Thêm các tham số bắt buộc

Nếu không chỉ định giá trị mặc định cho tham số, bạn phải luôn truyền đối số tương ứng.

  1. Trong Hello.kt, hãy viết một hàm shouldChangeWater() nhận 3 tham số: day, temperature và cấp độ dirty. Hàm này trả về true nếu cần thay nước (nếu là Chủ Nhật, nếu nhiệt độ quá cao hoặc nếu nước quá bẩn). Bạn phải cung cấp ngày trong tuần, nhưng nhiệt độ mặc định là 22 và mức độ bẩn mặc định là 20.

    Sử dụng biểu thức when mà không có đối số. Trong Kotlin, biểu thức này hoạt động như một loạt các lệnh kiểm tra if/else if.
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
    return when {
        temperature > 30 -> true
        dirty > 30 -> true
        day == "Sunday" ->  true
        else -> false
    }
}
  1. Gọi shouldChangeWater() từ feedTheFish() và cung cấp ngày. Tham số day không có giá trị mặc định, vì vậy bạn phải chỉ định một đối số. Hai tham số còn lại của shouldChangeWater() có giá trị mặc định, vì vậy bạn không cần truyền đối số cho chúng.
fun feedTheFish() {
    val day = randomDay()
    val food = fishFood(day)
    println ("Today is $day and the fish eat $food")
    println("Change water: ${shouldChangeWater(day)}")
}
=> Today is Thursday and the fish eat granules
Change water: false

Bước 3: Tạo hàm thu gọn

Biểu thức when mà bạn đã viết ở bước trước chứa rất nhiều logic trong một lượng nhỏ mã. Nếu muốn giải nén một chút hoặc nếu các điều kiện cần kiểm tra phức tạp hơn, bạn có thể sử dụng một số biến cục bộ có tên rõ ràng. Nhưng cách thực hiện trong Kotlin là dùng các hàm thu gọn.

Hàm thu gọn hoặc hàm một biểu thức là một mẫu phổ biến trong Kotlin. Khi một hàm trả về kết quả của một biểu thức, bạn có thể chỉ định phần nội dung của hàm sau ký hiệu =, bỏ qua dấu ngoặc nhọn {} và bỏ qua return.

  1. trong Hello.kt, hãy thêm các hàm thu gọn để kiểm thử các điều kiện.
fun isTooHot(temperature: Int) = temperature > 30

fun isDirty(dirty: Int) = dirty > 30

fun isSunday(day: String) = day == "Sunday"
  1. Thay đổi shouldChangeWater() để gọi các hàm mới.
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
    return when {
        isTooHot(temperature) -> true
        isDirty(dirty) -> true
        isSunday(day) -> true
        else  -> false
    }
}
  1. Chạy chương trình. Đầu ra từ println()shouldChangeWater() phải giống như trước khi bạn chuyển sang sử dụng các hàm thu gọn.

Giá trị mặc định

Giá trị mặc định cho một tham số không nhất thiết phải là một giá trị. Đó có thể là một hàm khác, như minh hoạ trong mẫu một phần sau:

fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = getDirtySensorReading()): Boolean {
    ...

Trong nhiệm vụ này, bạn sẽ tìm hiểu một chút về bộ lọc trong Kotlin. Bộ lọc là một cách hữu ích để lấy một phần danh sách dựa trên điều kiện nào đó.

Bước 1: Tạo bộ lọc

  1. Trong Hello.kt, hãy xác định danh sách các vật trang trí hồ cá ở cấp cao nhất bằng listOf(). Bạn có thể thay thế nội dung của Hello.kt.
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
  1. Tạo một hàm main() mới có một dòng để chỉ in những đồ trang trí bắt đầu bằng chữ "p". Mã cho điều kiện lọc nằm trong dấu ngoặc nhọn {}it đề cập đến từng mục khi bộ lọc lặp qua. Nếu biểu thức trả về true, mục sẽ được thêm vào.
fun main() {
    println( decorations.filter {it[0] == 'p'})
}
  1. Chạy chương trình của bạn và bạn sẽ thấy kết quả sau trong cửa sổ Chạy:
⇒ [pagoda, plastic plant]

Bước 2: So sánh bộ lọc eager và bộ lọc lazy

Nếu đã quen thuộc với các bộ lọc trong những ngôn ngữ khác, bạn có thể thắc mắc liệu các bộ lọc trong Kotlin là eager (chủ động) hay lazy (thụ động). Danh sách kết quả được tạo ngay lập tức hay khi truy cập vào danh sách? Trong Kotlin, điều này xảy ra theo bất kỳ cách nào bạn cần. Theo mặc định, filter là eager. Mỗi lần bạn dùng bộ lọc, một danh sách sẽ được tạo.

Để chuyển bộ lọc thành lazy, bạn có thể dùng Sequence. Đó là một tập hợp chỉ có thể xem xét một mục mỗi lúc, bắt đầu từ đầu và đi đến cuối. Điểm thuận tiện là đây đúng là API mà bộ lọc lazy cần.

  1. Trong Hello.kt, hãy thay đổi mã để chỉ định danh sách đã lọc cho một biến có tên là eager, sau đó in danh sách đó.
fun main() {
    val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")

    // eager, creates a new list
    val eager = decorations.filter { it [0] == 'p' }
    println("eager: " + eager)
  1. Bên dưới mã đó, hãy đánh giá bộ lọc bằng cách dùng Sequence với asSequence(). Chỉ định trình tự cho một biến có tên là filtered và in ra.
   // lazy, will wait until asked to evaluate
    val filtered = decorations.asSequence().filter { it[0] == 'p' }
    println("filtered: " + filtered)

Khi bạn trả về kết quả bộ lọc dưới dạng Sequence, biến filtered sẽ không lưu giữ danh sách mới mà sẽ lưu giữ Sequence của các phần tử trong danh sách và thông tin về bộ lọc sẽ áp dụng cho các phần tử đó. Mỗi khi bạn truy cập vào các thành phần của Sequence, bộ lọc sẽ được áp dụng và kết quả sẽ được trả về cho bạn.

  1. Buộc đánh giá trình tự bằng cách chuyển đổi trình tự đó thành một List bằng toList(). In kết quả.
    // force evaluation of the lazy list
    val newList = filtered.toList()
    println("new list: " + newList)
  1. Chạy chương trình rồi quan sát kết quả.
⇒ eager: [pagoda, plastic plant]
filtered: kotlin.sequences.FilteringSequence@386cc1c4
new list: [pagoda, plastic plant]

Để hình dung những gì đang xảy ra với Sequence và hoạt động đánh giá trì hoãn, hãy sử dụng hàm map(). Hàm map() thực hiện một phép biến đổi đơn giản trên từng phần tử trong chuỗi.

  1. Với danh sách decorations tương tự như trên, hãy thực hiện một phép biến đổi bằng map() mà không làm gì cả và chỉ trả về phần tử đã được truyền. Thêm một println() để cho biết mỗi lần một phần tử được truy cập và chỉ định trình tự cho một biến có tên là lazyMap.
    val lazyMap = decorations.asSequence().map {
        println("access: $it")
        it
    }
  1. In lazyMap, in phần tử đầu tiên của lazyMap bằng cách sử dụng first() và in lazyMap được chuyển đổi thành List.
    println("lazy: $lazyMap")
    println("-----")
    println("first: ${lazyMap.first()}")
    println("-----")
    println("all: ${lazyMap.toList()}")
  1. Chạy chương trình của bạn và quan sát kết quả. Việc in lazyMap chỉ in một tham chiếu đến Sequenceprintln() bên trong không được gọi. Việc in phần tử đầu tiên chỉ truy cập vào phần tử đầu tiên. Việc chuyển đổi Sequence thành List sẽ truy cập vào tất cả các phần tử.
⇒ lazy: kotlin.sequences.TransformingSequence@5ba23b66
-----
access: rock
first: rock
-----
access: rock
access: pagoda
access: plastic plant
access: alligator
access: flowerpot
all: [rock, pagoda, plastic plant, alligator, flowerpot]
  1. Tạo một Sequence mới bằng bộ lọc ban đầu trước khi áp dụng map. In kết quả đó.
    val lazyMap2 = decorations.asSequence().filter {it[0] == 'p'}.map {
        println("access: $it")
        it
    }
    println("-----")
    println("filtered: ${ lazyMap2.toList() }")
  1. Chạy chương trình của bạn và quan sát kết quả bổ sung. Tương tự như khi lấy phần tử đầu tiên, println() bên trong chỉ được gọi cho những phần tử được truy cập.
⇒
-----
access: pagoda
access: plastic plant
filtered: [pagoda, plastic plant]

Trong nhiệm vụ này, bạn sẽ được giới thiệu về lambda và hàm bậc cao trong Kotlin.

Hàm lambda

Ngoài các hàm thông thường, được đặt tên, Kotlin còn hỗ trợ hàm lambda. Lambda là một biểu thức giúp tạo hàm. Nhưng thay vì khai báo một hàm được đặt tên, bạn sẽ khai báo hàm không có tên. Điều khiến hàm này hữu ích là biểu thức lambda hiện có thể được chuyển ở dạng dữ liệu. Trong các ngôn ngữ khác, hàm lambda được gọi là hàm ẩn danh, hằng hàm hoặc các tên tương tự.

Hàm bậc cao hơn

Bạn có thể tạo một hàm bậc cao bằng cách truyền một lambda đến một hàm khác. Trong nhiệm vụ trước, bạn đã tạo một hàm bậc cao có tên là filter. Bạn đã truyền biểu thức lambda sau vào filter làm điều kiện để kiểm tra:
{it[0] == 'p'}

Tương tự, map là một hàm bậc cao và hàm lambda mà bạn truyền vào hàm này là phép biến đổi cần áp dụng.

Bước 1: Tìm hiểu về lambda

  1. Giống như các hàm được đặt tên, hàm lambda có thể chứa tham số. Đối với hàm lambda, các tham số (và loại tham số, nếu cần) nằm ở bên trái -> (mũi tên hàm). Mã cần thực thi nằm ở bên phải mũi tên hàm. Sau khi hàm lambda được chỉ định cho một biến, bạn có thể gọi biến đó giống như một hàm.

    Khi sử dụng REPL (Tools > Kotlin > Kotlin REPL – Công cụ > Kotlin > Kotlin REPL), hãy thử đoạn mã sau:
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10

Trong ví dụ này, hàm lambda lấy một Int có tên là dirty và trả về dirty / 2. (Vì lọc giúp loại bỏ bụi bẩn.)

  1. Cú pháp cho loại hàm của Kotlin có liên quan chặt chẽ với cú pháp cho hàm lambda. Hãy sử dụng cú pháp này để khai báo rõ ràng một biến lưu giữ hàm:
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }

Ý nghĩa của mã này như sau:

  • Tạo một biến có tên là waterFilter.
  • waterFilter có thể là bất kỳ hàm nào nhận một Int và trả về một Int.
  • Chỉ định một hàm lambda cho waterFilter.
  • Hàm lambda trả về giá trị của đối số dirty chia cho 2.

Xin lưu ý rằng bạn không phải chỉ định loại đối số của hàm lambda nữa. Loại sẽ được tính bằng khả năng dự đoán loại.

Bước 2: Tạo một hàm bậc cao

Cho đến nay, các ví dụ về lambda chủ yếu trông giống như các hàm. Sức mạnh thực sự của hàm lambda nằm ở khả năng dùng hàm này để tạo các hàm bậc cao hơn, trong đó đối số cho hàm này lại chính là hàm kia.

  1. Viết một hàm bậc cao. Sau đây là một ví dụ cơ bản, một hàm nhận 2 đối số. Đối số đầu tiên là một số nguyên. Đối số thứ hai là một hàm nhận một số nguyên và trả về một số nguyên. Hãy thử trong REPL.
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int {
   return operation(dirty)
}

Phần nội dung mã sẽ gọi hàm đã được chuyển làm đối số thứ hai và chuyển đối số đầu tiên cùng với đối số đó.

  1. Để gọi hàm này, hãy chuyển hàm ở dạng số nguyên và dạng hàm.
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
println(updateDirty(30, waterFilter))
⇒ 15

Hàm mà bạn truyền không nhất thiết phải là một lambda; thay vào đó, hàm này có thể là một hàm thông thường được đặt tên. Để chỉ định đối số là một hàm thông thường, hãy dùng toán tử ::. Bằng cách này, Kotlin biết rằng bạn đang truyền tham chiếu hàm dưới dạng đối số, chứ không phải đang cố gắng gọi hàm.

  1. Hãy thử truyền một hàm thông thường được đặt tên đến updateDirty().
fun increaseDirty( start: Int ) = start + 1

println(updateDirty(15, ::increaseDirty))
⇒ 16
var dirtyLevel = 19;
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
println(dirtyLevel)
⇒ 42
  • Để tạo một tệp nguồn Kotlin trong IntelliJ IDEA, hãy bắt đầu bằng một dự án Kotlin.
  • Để biên dịch và chạy một chương trình trong IntelliJ IDEA, hãy nhấp vào hình tam giác màu xanh lục bên cạnh hàm main(). Kết quả đầu ra sẽ xuất hiện trong cửa sổ nhật ký bên dưới.
  • Trong IntelliJ IDEA, hãy chỉ định các đối số dòng lệnh để truyền đến hàm main() trong Run > Edit Configurations (Chạy > Chỉnh sửa cấu hình).
  • Hầu hết mọi thứ trong Kotlin đều có giá trị. Bạn có thể sử dụng thực tế này để làm cho mã của mình ngắn gọn hơn bằng cách sử dụng giá trị của if hoặc when làm biểu thức hoặc giá trị trả về.
  • Các đối số mặc định giúp bạn không cần dùng nhiều phiên bản của một hàm hoặc phương thức. Ví dụ:
    fun swim(speed: String = "fast") { ... }
  • Hàm thu gọn hoặc hàm một biểu thức có thể giúp mã của bạn dễ đọc hơn. Ví dụ:
    fun isTooHot(temperature: Int) = temperature > 30
  • Bạn đã tìm hiểu một số kiến thức cơ bản về bộ lọc, sử dụng biểu thức lambda. Ví dụ:
    val beginsWithP = decorations.filter { it [0] == 'p' }
  • Biểu thức lambda là một biểu thức tạo ra một hàm không tên. Biểu thức lambda được xác định giữa dấu ngoặc nhọn {}.
  • Trong hàm bậc cao, bạn truyền một hàm (chẳng hạn như biểu thức lambda) đến một hàm khác dưới dạng dữ liệu. Ví dụ:
    dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}

Bài học này có rất nhiều nội dung, đặc biệt nếu bạn mới làm quen với lambda. Một bài học sau này sẽ đề cập lại về lambda và hàm bậc cao.

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 contains(element: String) trả về true nếu chuỗi element có trong chuỗi mà hàm được gọi. Kết quả của đoạn mã sau sẽ là gì?

val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")

println(decorations.filter {it.contains('p')})

[pagoda, plastic, plant]

[pagoda, plastic plant]

[pagoda, plastic plant, flowerpot]

[rock, alligator]

Câu hỏi 2

Trong định nghĩa hàm sau, tham số nào là bắt buộc?
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20, numDecorations: Int = 0): Boolean {...}

numDecorations

dirty

day

temperature

Câu hỏi 3

Bạn có thể truyền một hàm thông thường được đặt tên (không phải kết quả của việc gọi hàm đó) cho một hàm khác. Bạn sẽ truyền increaseDirty( start: Int ) = start + 1 đến updateDirty(dirty: Int, operation: (Int) -> Int) như thế nào?

updateDirty(15, &increaseDirty())

updateDirty(15, increaseDirty())

updateDirty(15, ("increaseDirty()"))

updateDirty(15, ::increaseDirty)

Chuyển sang bài học tiếp theo: 4. Lớp và đối tượng

Để 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".