Kotlin Bài 5: Sử dụng hàm trong Kotlin

Ở bài trước mình đã trình bày về các cấu trúc điều kiện và vòng lặp trong Kotlin. Bài hôm nay mình sẽ giới thiệu về cách định nghĩa và sử dụng hàm trong Kotlin, sau đó là nói về cách tổ chức code sử dụng package.

[toc]

Hàm (Function)

Hàm là 1 tập hợp các đoạn câu lệnh để thực hiện công việc nào đó. Hàm có thể có tên và cũng có thể không. Việc sử dụng hàm sẽ khiến cho code của ta ngắn gọn hơn và không bị lặp đi lặp lại các đoạn code.

Khai báo và gọi hàm

Trong Kotlin, ta định nghĩa hàm sử dụng từ khóa fun:

fun helloMessage(name: String): String {
    return "Hello, $name"
}

Và thử gọi hàm qua câu lệnh:

val helloMessage = helloMessage("Duong Vu")
println(helloMessage) // in ra: Hello, Duong Vu

Trong ví dụ trên, ta đã khai báo hàm helloMessage với 1 tham số đầu vào là name có kiểu String. Hàm này trả về kiểu dữ liệu String. Cách khai báo tham số khi định nghĩa hàm là:

tên_tham_số:kiểu_dữ_liệu

Lưu ý: Hàm có thể có hoặc không có tham số.

Ta xem thử 1 ví dụ khác:

fun sayHello(name: String):Unit{
    print("Hello, $name")
}

Và gọi hàm sayHello:

sayHello("Duong Vu") // in ra: Hello, Dương Vu

Ta thấy là hàm sayHello ở trên cũng tương tự với hàm helloMessage ta viết lúc đầu. Chỉ khác 1 điều là hàm sayHello trả về kiểu dữ liệu Unit (thay vì String) và tự nó print ra câu chào. Bạn có thể thấy hàm sayHello không có câu lệnh return ở cuối hàm. Bởi đơn giản, kiểu dữ liệu Unit là kiểu dữ liệu đặc biệt, khi hàm trả về kiểu dữ liệu này, ta không bắt buộc phải có câu lệnh return để trả về kết quả cho hàm. Unit trong Kotlin giống như Void trong C và Java vậy. Và trong Kotlin, khi bạn định nghĩa 1 hàm mà không khai báo kiểu dữ liệu trả về, thì tức là hàm của bạn đã trả về kiểu Unit:

//Kết quả vẫn giống hệt như khi có khai báo trả về kiểu Unit
fun sayHello(name: String) {
    print("Hello, $name")
}

Định nghĩa giá trị mặc định của tham số:

Ta viết lại hàm sayHello như thế này:

fun sayHello(name: String = "Dương vũ") {
   print("Hello, $name!!!")
}

Như ta thấy, so với hàm đã viết ở phần 1, tham số đầu vào name có thêm định nghĩa = “Dương vũ” điều này tức là “Dương Vũ” là giá trị mặc định của tham số name. Nếu ta gọi hàm sayHello mà không truyền tham số name, Kotlin sẽ sử dụng giá trị mặc định của tham số name đó. Thử gọi hàm:

sayHello() // Kết quả: Hello, Dương Vũ

Còn tất nhiên khi ta truyền giá trị cho tham số name thì giá trị mặc định sẽ không được sử dụng nữa:

sayHello("Mr.Rain") // Kết quả: Hello, Mr.Rain

Việc Kotlin cho phép định nghĩa giá trị mặc định của tham số đã giúp cho ta giảm thiểu số lượng hàm phải viết. Nếu như trong Java, không có tính năng này, ta đã phải viết 2 hàm sayHello(name)sayHello() để xử lý các trường hợp có và không có tham số đầu vào rồi. Rất tiện đúng không?

Sử dụng tên cho tham số

Sử dụng tên cho tham số là gì và vì sao trong một số trường hợp bắt buộc phải cần đến nó. Hãy cùng mình xem 1 ví dụ. Ta sẽ định nghĩa 1 hàm như sau:

fun getFullName(firstName: String, middleName: String = "Van", lastName: String) {
    print("Full name: $firstName $middleName $lastName");
}

Ở trên ta khai báo 1 hàm với 3 tham số đầu vào: firstName, middleName, lastName. Trong đó middleName có giá trị mặc định là Van. Như mình đã nói ở phần trên, nếu không truyền tham số middleName thì Kotlin sẽ sử dụng giá trị mặc định của nó (là Van) đúng không? Giờ ta thử gọi hàm mà không truyền middleName vào xem sao:

getFullName("Tran", "Nam") //lỗi No value passed for parameter lastName

Lúc này, ta sẽ nhận được thông báo lỗi: No value passed for parameter lastName (chưa truyền tham số lastName). Vì sao? Vì khi này, Kotlin hiểu rằng tham số thứ 2 (có giá trị “Nam“) là middleName, chứ không phải là lastName như ta mong muốn. Khi này ta cần phải làm như sau:

getFullName("Tran", lastName = "Nam") // In ra Full name: Tran Van Nam

Lúc này ta đã chỉ rõ “Nam” là giá trị của tham số lastName chứ không phải của middleName, và middleName do không truyền vào nên sử dụng giá trị mặc định.

Lưu ý: Việc truyền tên tham số trong lúc gọi hàm đôi khi không bắt buộc, nhưng nó làm cho code của ta dễ đọc hơn, vì vậy mình khuyến khích khi sử dụng hàm, hãy cố gắng truyền thêm tên tham số vào, để sau này đọc lại code không bị vất vả.

Hàm với 1 dòng lệnh duy nhất – Single line function

Khi giá trị trả về của 1 hàm có thể viết được bằng 1 dòng lệnh duy nhất, ta có thể bỏ cặp {} và từ khóa return để đơn giản hóa. Ví dụ:

fun getSum(numberOne: Int, numberTow: Int) = numberOne + numberTow

Hàm trên tính tổng của 2 số, và ta chỉ cần duy nhất 1 dòng để định nghĩa hàm, ko cần đến {} và từ khóa return, rất ngắn gọn.

Thêm 1 ví dụ nữa:

fun doubleNumber(number:Int) = 2*number

Hàm không giới hạn tham số

Đôi khi ta không biết trước số lượng tham số cần truyền vào cho hàm. Ta có thể cần truyền 1, 2 hay thậm chí 100 tham số. Lúc này ta cần định nghĩa 1 hàm không giới hạn tham số. Ví dụ như sau:

fun getSum(vararg numbers: Int): Int {
    var sum = 0
    for (n in numbers) {
        sum = sum + n
    }
    return sum
}

Như các bạn thấy ta đã thêm từ khóa vararg vào trước tham số numbers (kiểu Int). Có nghĩa là ở đây ta có thể truyền bao nhiêu tham số (kiểu Int) tùy thích. Và hàm getSum này sẽ tính tổng của tất cả các tham số truyền vào. Thử gọi hàm:

print(getSum(1,2,3)) // In ra 6
print(getSum(1,2,3,4,5)) // In ra 15

Hàm cục bộ

Kotlin cho phép ta định nghĩa hàm trong thân 1 hàm khác.

fun printGreeting(firstName: String, lastName: String) {

   //Bắt đầu định nghĩa hàm cục bộ
   fun getFullName(firstName: String, lastName: String): String {
       return "$firstName $lastName"
   }
   //Sử dụng hàm cục bộ
   println("Hello ${getFullName(firstName, lastName)}")

}

Vẫn là hàm in ra 1 câu chào, đầu vào là firstName lastName. Nhưng lần này, trong thân hàm  printGreeting ta đã định nghĩa thêm 1 hàm khác, getFullName trả về tên đầy đủ dựa vào 2 tham số đầu vào firstName lastName của hàm printGreeting Và hàm printGreeting sẽ sử dụng giá trị trả về từ hàm getFullName. Thử gọi hàm:

printGreeting("Duong", "Vu") // Kết quả: Hello Duong Vu

Lưu ý: Hàm cục bộ chỉ có thể sử dụng được trong thân hàm mà nó được định nghĩa.

Như vậy là trên đây mình đã giới thiệu cơ bản về khai báo hàm và cách sử dụng hàm trong nhiều trường hợp. Vẫn còn 1 số kiến thức khác nâng cao hơn như lambda function, extension function, … mình sẽ trình bày trong những bài sau, trong những tình huống phù hợp hơn. Còn bây giờ, ta chỉ cần biết được cơ bản về hàm trong Kotlin, vậy là đủ nhỉ ;).

Packages

Nếu bạn từng làm việc với Java, chắc hẳn bạn đã biết rằng Java sử dụng các gói (package) để chứa các lớp có liên quan với nhau. Ví dụ như package java.util chứa các lớp tiện ích mà ta thường sử dụng như Date, HashMap, Collections, … Ở trong Kotlin, package cũng có khái niệm tương tự. Tuy nhiên package trong Kotlin không chỉ chứa được các lớp mà nó còn có thể chứa các hàm (lớp là gì thì mình sẽ trình bày ngay bài sau). Và việc định nghĩa package ở trong Kotlin cũng cực kỳ dễ dàng. Trong Kotlin, các package được định nghĩa với từ khóa package.

Định nghĩa package

Giờ mình sẽ hướng dẫn các bạn cách định nghĩa, khai báo 1 package. Các bạn hãy tạo 1 project mới trong IntelliJ IDEA. Tạo 1 file Kotlin đặt tên là Utils.kt:

Tạo file Utils.kt

Note: Nếu bạn còn đang thắc mắc về cách tạo project, tạo file/class thì hãy đọc lại bài hướng dẫn sử dụng IntelliJ IDEA của mình nhé.

Giờ mình sẽ định nghĩa packge Utils như sau:

package Utils

Quá đơn giản đúng không? Vậy là ta đã định nghĩa xong package Utils bằng từ khóa package rồi. Giờ, bên trong file Utils.kt, ta sẽ định nghĩa thêm 1 lớp MyClass nữa (ta sẽ tìm hiểu về lớp trong các bài viết sau):

class MyClass

Việc làm trên có nghĩa là ta đã định nghĩa lớp MyClass nằm trong package Utils. Và tên đầy đủ  (fully qualified name) của lớp MyClass là: Utils.MyClass. Vậy tên đầy đủ của 1 class nằm trong 1 package sẽ là

tên_package.tên_class

Giờ ta sẽ thử định nghĩa 1 hàm trong package Utils như sau:

fun getPi():Float{
    return 3.14f
}

Cũng tương tự như lớp MyClass ta vừa tạo ở trên, hàm getPi sẽ có tên đầy đủUtils.getPi(). Tên đầy đủ dùng để làm gì, mình sẽ trình bày ngay ở phần dưới.

Import package

Trong Kotlin, để sử dụng các lớp, hàm, … nói chung là mọi thứ trong package, ta cần phải import package đó. Để làm việc này, ta sử dụng từ khóa import. Bây giờ ta sẽ thử sử dụng package Utils mà ta đã định nghĩa ở trên. Ta sẽ tạo 1 file mới trong project, đặt tên cho nó là Main.kt. Ta viết sẵn cho file Main.kt này 1 hàm main như sau để chuẩn bị cho việc viết code:

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

Rồi Ok, giờ ta sẽ import package Utils đã viết trước đó. Thêm dòng sau vào ngay đầu file Main.kt:

import Utils.*

Câu lệnh này đã import toàn bộ package Utils vào file Main.kt. Giờ ta có thể sử dụng bất cứ thứ gì có trong package Utils. Trong hàm main của file Main.kt, Ta sẽ thử gọi hàm getPi() của package Utils:

print(getPi()) // in ra 3.14

Lưu ý: nếu ta không muốn sử dụng toàn bộ package, mà chỉ muốn sử dụng 1 hàm (hoặc lớp) nào đó trong package, ta có thể import riêng hàm (lớp) đó:

import Utils.getPi

Như vậy là ta đã sử dụng được hàm getPi trong package Utils. Giờ ta sẽ thử xóa dòng lệnh import bên trên đi xem sao. Ngay lập tức sẽ nhận được thông báo lỗi: Unresolved reference: getPi (không nhận biết được hàm getPi). Như vậy là nếu không import package Utils, ta sẽ không sử dụng được các thành phần của nó.

Sử dụng tên đầy đủ (fully qualified name) của các thành phần trong package

Nếu bạn không muốn import 1 package mà vẫn muốn sử dụng các thành phần của nó (vì dụ như hàm, lớp…), bạn cần phải sử dụng tên đầy đủ cùa thành phần đó:

print(Utils.getPi()) // in ra 3.14

Đây chính là vai trò của tên đầy đủ mà mình đã trình bày ở phần định nghĩa package. Nó giúp chúng ta không cần phải import 1 package mà vẫn sử dụng được các thành phần trong package đó. Tên đầy đủ của 1 hàm, lớp còn rất quan trọng trong trường hợp sau: Khi ta sử dụng nhiều package mà các package đó có các hàm trùng tên nhau.

Mình tạo ra 1 package khác có tên là Utils2, và cùng định nghĩa hàm getPi cho package Utils2 đó:

fun getPi():Float = 6.28f

Giờ trong file Main.kt, ta vừa muốn sử dụng hàm getPi trong package Utils, vừa muốn sử dụng getPi trong package Utils2, lúc này ta cũng cần tới tên đầy đủ:

println("getPi at Utils: ${Utils.getPi()}") // in ra 3.14
println("getPi at Utils2: ${Utils2.getPi()}") // in ra 6.28

Tổng kết

Như vậy bài này mình đã trình bày xong cơ bản về cách khai báo và sử dụng hàm trong Kotlin. Mình cũng đã nói xong về vấn để sử dụng package. Tất nhiên những thứ trên đây không phải là tất cả về hàm hay package trong Kotlin, ta còn rất nhiều thứ nâng cao hơn, mới mẻ hơn. Mình sẽ trình bày trong các bài tiếp theo. Hãy theo dõi nhé.

One thought on “Kotlin Bài 5: Sử dụng hàm trong Kotlin”

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *