Chào mừng độc giả Cafedev đến với hành trình khám phá về Hàm trong Kotlin. Tại Cafedev, chúng tôi tin rằng việc hiểu biết sâu rộng về các hàm trong Kotlin sẽ làm tăng sức mạnh lập trình của bạn. Chúng ta sẽ cùng nhau tìm hiểu về cách khai báo, sử dụng tham số và giá trị trả về, cũng như những tính năng mạnh mẽ như tham số mặc định và biểu thức lambda. Hãy bắt đầu hành trình này để nâng cao kỹ năng lập trình của bạn và hiểu sâu về Functions trong Kotlin!”

Bạn có thể khai báo các hàm của riêng bạn trong Kotlin bằng cách sử dụng từ khóa fun.

fun hello() {
  return println("Hello, world!")
}
fun main() {
 hello()
// Hello, world!
}

Trong Kotlin:

  • Các tham số của hàm được viết trong dấu ngoặc ( ).
  • Mỗi tham số phải có một kiểu dữ liệu, và nhiều tham số phải được phân tách bằng dấu phẩy ,.
    • Kiểu trả về được viết sau dấu ngoặc của hàm (), phân tách bằng dấu hai chấm :.
  • Thân hàm được viết trong dấu ngoặc nhọn {}.
  • Từ khóa return được sử dụng để thoát hoặc trả về một giá trị từ hàm.

Nếu một hàm không trả về bất kỳ điều gì hữu ích, kiểu trả về và từ khóa return có thể được bỏ qua. Tìm hiểu thêm về điều này tại Hàm không có giá trị trả về.
Trong ví dụ sau:

  • xy là các tham số của hàm.
  • xy có kiểu Int.
  • Kiểu trả về của hàm là Int.
  • Hàm trả về tổng của xy khi được gọi.
fun sum(x: Int, y: Int): Int {
 return x + y
}
fun main() {
 println(sum(1, 2))
// 3
}

Chúng tôi khuyến nghị trong các quy ước lập trình của mình rằng bạn nên đặt tên cho các hàm bắt đầu bằng một chữ thường và sử dụng kiểu chữ gạch nối camel case mà không có gạch dưới.

1. Đối số có tên

Đối với code ngắn gọn, khi gọi hàm của bạn, bạn không cần phải bao gồm tên tham số. Tuy nhiên, việc bao gồm tên tham số làm cho mã nguồn của bạn dễ đọc hơn. Điều này được gọi là sử dụng đối số có tên. Nếu bạn bao gồm tên tham số, bạn có thể viết các tham số theo bất kỳ thứ tự nào.

Trong ví dụ sau, mẫu chuỗi ($) được sử dụng để truy cập các giá trị tham số, chuyển đổi chúng thành kiểu String, và sau đó nối chúng vào một chuỗi để in.

fun printMessageWithPrefix(message: String, prefix: String) {
println("[$prefix] $message")
}
fun main() {
// Uses named arguments with swapped parameter order
printMessageWithPrefix(prefix = "Log", message = "Hello")
// [Log] Hello
}

2. Giá trị mặc định của tham số

Bạn có thể xác định giá trị mặc định cho các tham số của hàm. Bất kỳ tham số nào có giá trị mặc định đều có thể được bỏ qua khi gọi hàm của bạn. Để khai báo giá trị mặc định, sử dụng toán tử gán = sau kiểu:

fun printMessageWithPrefix(message: String, prefix: String = "Info") {
println("[$prefix] $message")
}
fun main() {
// Function called with both parameters
printMessageWithPrefix("Hello", "Log")
// [Log] Hello

// Function called only with message parameter
printMessageWithPrefix("Hello")
// [Info] Hello

printMessageWithPrefix(prefix = "Log", message = "Hello")
// [Log] Hello
}

Bạn có thể bỏ qua các tham số cụ thể có giá trị mặc định, thay vì bỏ qua tất cả chúng. Tuy nhiên, sau tham số đầu tiên bị bỏ qua, bạn phải đặt tên cho tất cả các tham số tiếp theo.

3. Hàm không có giá trị trả về

Nếu hàm của bạn không trả về giá trị hữu ích, kiểu trả về của nó là Unit. Unit là một kiểu với chỉ một giá trị – Unit. Bạn không cần phải khai báo rằng Unit được trả về một cách rõ ràng trong thân hàm của bạn. Điều này có nghĩa là bạn không cần phải sử dụng từ khóa return hoặc khai báo kiểu trả về:

fun printMessage(message: String) {
println(message)
// `return Unit` or `return` is optional
}


fun main() {
printMessage("Hello")
// Hello
}

4. Hàm với một biểu thức

Để làm cho mã nguồn của bạn ngắn gọn hơn, bạn có thể sử dụng các hàm với một biểu thức. Ví dụ, hàm sum() có thể được rút gọn:

fun sum(x: Int, y: Int): Int {
 return x + y
}
fun main() {
 println(sum(1, 2))
 // 3
}

Bạn có thể loại bỏ dấu ngoặc nhọn {} và khai báo thân hàm bằng toán tử gán =. Và do suy luận kiểu của Kotlin, bạn cũng có thể bỏ qua kiểu trả về. Hàm sum() sau đó trở thành một dòng:

fun sum(x: Int, y: Int) = x + y

fun main() {
println(sum(1, 2))
// 3
}

Việc bỏ qua kiểu trả về là khả năng khi hàm của bạn không có thân ({}). Trừ khi kiểu trả về của hàm là Unit.

5. Thực hành về hàm

5.1 Bài tập 1

Viết một hàm có tên là circleArea nhận bán kính của một hình tròn dưới dạng tham số và xuất ra diện tích của hình tròn đó.

Trong bài tập này, bạn nhập một gói để có thể truy cập giá trị của pi thông qua PI. Để biết thêm thông tin về nhập gói(package), xem bài Gói và nhập gói.

import kotlin.math.PI

fun circleArea() {
// Write your code here
}
fun main() {
println(circleArea(2))
}

Gợi ý:

import kotlin.math.PI

fun circleArea(radius: Int): Double {
return PI * radius * radius
}

fun main() {
println(circleArea(2)) // 12.566370614359172
}

5.2 Bài tập 2

Viết lại hàm circleArea từ bài tập trước dưới dạng hàm một biểu thức.

import kotlin.math.PI

// Write your code here

fun main() {
  println(circleArea(2))
}

Gợi ý:

import kotlin.math.PI

fun circleArea(radius: Int): Double = PI * radius * radius

fun main() {
 println(circleArea(2)) // 12.566370614359172
}

5.3 Bài tập 3

Bạn có một hàm chuyển đổi một khoảng thời gian được chỉ định bằng giờ, phút và giây thành giây. Trong hầu hết các trường hợp, bạn chỉ cần truyền một hoặc hai tham số cho hàm trong khi những tham số còn lại bằng 0. Cải thiện hàm và mã gọi nó bằng cách sử dụng giá trị mặc định cho tham số và đối số có tên để mã nguồn trở nên dễ đọc hơn.

fun intervalInSeconds(hours: Int, minutes: Int, seconds: Int) =
((hours * 60) + minutes) * 60 + seconds

fun main() {
println(intervalInSeconds(1, 20, 15))
println(intervalInSeconds(0, 1, 25))
println(intervalInSeconds(2, 0, 0))
println(intervalInSeconds(0, 10, 0))
println(intervalInSeconds(1, 0, 1))
}

Gợi ý:

fun intervalInSeconds(hours: Int = 0, minutes: Int = 0, seconds: Int = 0) =
((hours * 60) + minutes) * 60 + seconds

fun main() {
 println(intervalInSeconds(1, 20, 15))
 println(intervalInSeconds(minutes = 1, seconds = 25))
 println(intervalInSeconds(hours = 2))
 println(intervalInSeconds(minutes = 10))
 println(intervalInSeconds(hours = 1, seconds = 1))
}

6. Biểu thức lambda

Kotlin cho phép bạn viết mã nguồn ngắn gọn hơn cho các hàm bằng cách sử dụng biểu thức lambda.
Ví dụ, hàm uppercaseString() sau đây:

fun uppercaseString(string: String): String {
 return string.uppercase()
}
fun main() {
 println(uppercaseString("hello"))
 // HELLO
}

Cũng có thể được viết dưới dạng biểu thức lambda:

fun main() {
 println({ string: String -> string.uppercase() }("hello"))
 // HELLO
}

Biểu thức lambda có thể khó hiểu khi nhìn sơ bộ, vì vậy hãy phân tích cụ thể. Biểu thức lambda được viết trong dấu ngoặc nhọn {}.
Trong biểu thức lambda, bạn viết:

  • Các tham số theo sau: ->.
  • Thân hàm sau: ->.

Trong ví dụ trước:

  • string là một tham số của hàm.
  • String có kiểu String.
  • Hàm trả về kết quả của hàm .uppercase() gọi trên string.

Nếu bạn khai báo một biểu thức lambda không có tham số, thì không cần sử dụng ->. Ví dụ:

{ println("Log message") }

Biểu thức lambda có thể được sử dụng theo nhiều cách. Bạn có thể:

6.1 Gán vào biến

Để gán một biểu thức lambda vào một biến, sử dụng toán tử gán =:

fun main() {
 val upperCaseString = { string: String -> string.uppercase() }
 println(upperCaseString("hello"))
 // HELLO
}

6.2 Truyền cho một hàm khác

Một ví dụ tuyệt vời khi việc truyền một biểu thức lambda cho một hàm là sử dụng hàm .filter() trên các bộ sưu tập:

fun main() {
 //sampleStart
 val numbers = listOf(1, -2, 3, -4, 5, -6)
 val positives = numbers.filter { x -> x > 0 }
 val negatives = numbers.filter { x -> x < 0 }
 println(positives)
 // [1, 3, 5]
 println(negatives)
 // [-2, -4, -6]
 //sampleEnd
}

Hàm .filter() chấp nhận một biểu thức lambda như một predicate(thuộc tính):
* { x -> x > 0 } lấy từng phần tử của danh sách và chỉ trả về những phần tử là số dương.
* { x -> x < 0 } lấy từng phần tử của danh sách và chỉ trả về những phần tử là số âm.

Nếu một biểu thức lambda là tham số hàm duy nhất, bạn có thể bỏ qua dấu ngoặc của hàm (). Đây là một ví dụ về lambda trailing, được thảo luận chi tiết hơn ở cuối bài này.
Một ví dụ khác tốt là sử dụng hàm
.map() để biến đổi các mục trong một bộ sưu tập:

fun main() {
 //sampleStart
 val numbers = listOf(1, -2, 3, -4, 5, -6)
 val doubled = numbers.map { x -> x * 2 }
 val tripled = numbers.map { x -> x * 3 }
 println(doubled)
 // [2, -4, 6, -8, 10, -12]
 println(tripled)
 // [3, -6, 9, -12, 15, -18]
 //sampleEnd
}

Hàm .map() chấp nhận một biểu thức lambda như một hàm biến đổi:
* { x -> x * 2 } lấy từng phần tử của danh sách và trả về phần tử đó nhân với 2.
* { x -> x * 3 } lấy từng phần tử của danh sách và trả về phần tử đó nhân với 3

6.3 Loại hàm

Trước khi bạn có thể trả về một biểu thức lambda từ một hàm, bạn cần hiểu rõ về loại hàm.
Bạn đã học về các loại cơ bản nhưng chính các hàm cũng có một loại. Sự suy luận kiểu của Kotlin có thể suy ra loại hàm từ kiểu tham số. Tuy nhiên, có thể có những lúc bạn cần chỉ định một cách rõ ràng loại hàm. Bộ biên dịch cần loại hàm để biết được điều gì được và không được phép cho hàm đó.
Cú pháp cho một loại hàm bao gồm:
* Mỗi kiểu tham số được viết trong dấu ngoặc ( ) và phân tách bằng dấu phẩy ,.
* Kiểu trả về được viết sau ->.
Ví dụ: (String) -> String hoặc (Int, Int) -> Int.
Đây là cách biểu thức lambda trông như nếu một loại hàm cho upperCaseString() được định nghĩa:

val upperCaseString: (String) -> String = { string -> string.uppercase() }

fun main() {
 println(upperCaseString("hello"))
// HELLO
}

Nếu biểu thức lambda của bạn không có tham số thì dấu ngoặc () được để trống. Ví dụ: () -> Unit

Bạn phải khai báo kiểu tham số và kiểu trả về hoặc trong biểu thức lambda hoặc như một loại hàm. Nếu không, bộ biên dịch sẽ không thể biết được loại của biểu thức lambda của bạn là gì. Ví dụ, điều sau đây sẽ không hoạt động: val upperCaseString = { str - str.uppercase() }

6.4 Trả về từ một hàm

Biểu thức lambda có thể được trả về từ một hàm. Để bộ biên dịch hiểu loại biểu thức lambda được trả về là gì, bạn phải khai báo một loại hàm.
Trong ví dụ sau, hàm toSeconds() có loại hàm là (Int) -> Int vì nó luôn trả về một biểu thức lambda nhận tham số kiểu Int và trả về một giá trị kiểu Int.
Ví dụ này sử dụng biểu thức when để xác định biểu thức lambda nào được trả về khi gọi toSeconds():

fun toSeconds(time: String): (Int) -> Int = when (time) {
"hour" -> { value -> value * 60 * 60 }
"minute" -> { value -> value * 60 }
"second" -> { value -> value }
else -> { value -> value }
}

fun main() {
val timesInMinutes = listOf(2, 10, 15, 1)
val min2sec = toSeconds("minute")
val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
println("Total time is $totalTimeInSeconds secs")
// Total time is 1680 secs
}

6.5 Gọi một cách độc lập

Biểu thức lambda có thể được gọi một cách độc lập bằng cách thêm dấu ngoặc () sau dấu ngoặc nhọn {} và bao gồm bất kỳ tham số nào trong dấu ngoặc đó:

fun main() {
//sampleStart
println({ string: String -> string.uppercase() }("hello"))
// HELLO
//sampleEnd
}

6.6 Lambda tham số cuối cùng

Như bạn đã thấy, nếu biểu thức lambda là tham số hàm duy nhất, bạn có thể bỏ qua dấu ngoặc của hàm (). Nếu một biểu thức lambda được truyền như tham số cuối cùng của một hàm, thì biểu thức có thể được viết bên ngoài dấu ngoặc của hàm (). Trong cả hai trường hợp, cú pháp này được gọi là lambda cuối cùng.
Ví dụ, hàm .fold() chấp nhận một giá trị khởi tạo và một hoạt động:

fun main() {
//sampleStart
// The initial value is zero.
// The operation sums the initial value with every item in the list cumulatively.
println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // 6

// Alternatively, in the form of a trailing lambda
println(listOf(1, 2, 3).fold(0) { x, item -> x + item }) // 6
//sampleEnd
}

Để biết thêm về biểu thức lambda, xem Biểu thức lambda và hàm ẩn danh.
Bước tiếp theo trong hành trình của chúng ta là tìm hiểu về các lớp trong Kotlin.

7. Thực hành biểu thức lambda

7.1 Bài tập 1

Bạn có một danh sách các hành động được hỗ trợ bởi một dịch vụ web, một tiền tố chung cho tất cả các yêu cầu và một ID của một tài nguyên cụ thể. Để yêu cầu một hành động title trên tài nguyên với ID: 5, bạn cần tạo URL sau: https://example.com/book-info/5/title. Sử dụng biểu thức lambda để tạo danh sách các URL từ danh sách các hành động.

fun main() {
val actions = listOf("title", "year", "author")
val prefix = "https://example.com/book-info"
val id = 5
val urls = // Write your code here
println(urls)
}

Gợi ý:

fun main() {
val actions = listOf("title", "year", "author")
val prefix = "https://example.com/book-info"
val id = 5
val urls = actions.map { action -> "$prefix/$id/$action" }
println(urls)
}

7.2 Bài tập 2

Viết một hàm nhận một giá trị Int và một hành động (một hàm với kiểu () -> Unit) sau đó lặp lại hành động một số lần nhất định. Sau đó, sử dụng hàm này để in ra “Hello” 5 lần.

fun repeatN(n: Int, action: () -> Unit) {
// Write your code here
}

fun main() {
// Write your code here
}

Gợi ý:

fun repeatN(n: Int, action: () -> Unit) {
 for (i in 1..n) {
 action()
}
}
fun main() {
 repeatN(5) {
 println("Hello")
}
}

-> Kho tài liệu Free học Kotlin từ A->Z

Cuối cùng,

Cảm ơn bạn đã đồng hành cùng chúng tôi trên chủ đề “Learn Functions in Kotlin” tại Cafedev. Chúng tôi hy vọng rằng thông tin chi tiết về khai báo, sử dụng hàm, cũng như các tính năng nâng cao như tham số mặc định và biểu thức lambda đã giúp bạn mở rộng kiến thức và kỹ năng lập trình của mình. Tại Cafedev, chúng tôi cam kết tiếp tục chia sẻ kiến thức và cập nhật những xu hướng mới nhất trong lĩnh vực lập trình. Hãy tiếp tục theo dõi để không bỏ lỡ những thông tin hữu ích và cùng nhau xây dựng cộng đồng lập trình sôi động tại Cafedev!

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của Cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!