Kotlin Bài 6: Sử dụng hàm trong Kotlin (nâng cao)

Xin chào, mình là Vũ. Ở bài viết trước mình đã giới thiệu cơ bản về cách khai báo và sử dụng hàm trong Kotlin. Trong bài này mình sẽ trình bày các kiến thức nâng cao hơn (và thú vị hơn) về hàm trong Kotlin. Chúng là top-level function, lambda function, là extension function, và còn nhiều nhiều nữa. Hãy cùng theo dõi nhé.

Trước khi bắt đầu, hãy tạo cho mình 1 project mới và tạo 1 file Main.kt với hàm main. Main.kt sẽ là nơi để ta viết các đoạn code demo. Nếu bạn chưa biết làm những điều này, vui lòng tham khảo lại bài viết hướng dẫn sử dụng IntelleJ IDEA của mình.

[toc]

Top-level function

Top-level function là các hàm không nằm trong bất kỳ 1 lớp (class) nào. Chúng được định nghĩa trong các package và được sử dụng bằng cách gọi trực tiếp qua tên đầy đủ (trong trường hợp không import package) hoặc tên hàm (trong trường hợp đã import package). Nếu bạn từng làm việc với Java chắc đều đã quen thuộc với các hàm static nằm trong lớp Utils. Top-level function trong Kotlin hoàn toàn tương tự như static function trong Java.

Hàm getPi bài trước mình định nghĩa chính là 1 top-level function. Giờ để hiểu rõ thêm sẽ làm thêm 1 ví dụ khác nữa. Ta tạo 1 file đặt tên là Utility.kt, định nghĩa package com.duongvu.utils và khai báo 1 hàm getCurrentDate trong package đó:

package com.duongvu.utils

fun getCurrentDate(): String {
    val date = Date()
    val dateFormat = "dd/MM/yyyy"
    val sdf = SimpleDateFormat(dateFormat)
    return sdf.format(date)
}

Lưu ý: tên packge không nhất thiết phải giống tên file chứa nó. Như ở trên mình đã định nghĩa package com.duongvu.utils trong file Utility.kt, điều này hoàn toàn OK.

Lưu ý: Bạn chưa cần phải hiểu rõ từng dòng trong hàm getCurrentDate ở trên, chỉ cần hiểu nó trả về ngày hiện tại dưới dạng 1 String. Và hàm này sử dụng các lớp java.util.Datejava.text.SimpleDateFormat, nên ta cần import 2 lớp trên:

import java.text.SimpleDateFormat
import java.util.Date

Giờ ta đã có thể sử dụng hàm getCurrentDate trong file Main.kt như sau:

import com.duongvu.utils.getCurrentDate

//Sử dụng bằng cách import hàm getCurrentDate trong package com.duongvu.utils
fun main(args: Array<String>) {
    print(getCurrentDate()) //in ra ngày hiện tại
}

hoặc:

import com.duongvu.utils.*

//Sử dụng bằng cách import tất cả mọi thứ trong package com.duongvu.utils
fun main(args: Array<String>) {
    print(getCurrentDate())
}

hoặc:

//Sử dụng bằng cách gọi tên đầy đủ của hàm getCurrentDate
fun main(args: Array<String>) {
    print(com.duongvu.utils.getCurrentDate())
}

Đó là tất cả các cách sử dụng 1 top-level function.

Lambda function

Lambda function là gì? Cấu trúc của lambda function?

Lambda function là các hàm không có tên. Chúng thường được sử dụng như các tham số để truyền vào 1 hàm khác (mình sẽ trình bày ở phần dưới). Lambda function còn có thể được biểu diễn dưới dạng các biến. Để rõ hơn hãy nhìn vào ví dụ của mình, trong file Main.kt mình sẽ khai báo 1 lambda function như thế này:

var message = { 
    print("Kotlin is awesome :D")
}

Ở trên mình đã định nghĩa 1 lambda function bởi 1 cặp {} và gán lambda function này vào biến message. Như các bạn thấy hàm này không cần định nghĩa bởi từ khóa fun, nó không hề có tên, cũng không hề có kiểu trả về. Trong hàm main, ta sẽ sử dụng nó như sau:

message() // in ra : Kotlin is awesome :D

Giờ ta sẽ nâng cấp lambda function trên 1 chút, ta sẽ truyền thêm tham số cho nó:

val message = {
    str:String->
    println(str)
    println("End lambda function")
}

Gọi hàm:

message("Kotlin is awesome :D") //in ra : Kotlin is awesome
                                //        End lambda function

Như vậy, nếu lambda function có tham số đầu vào thì các tham số sẽ được khai báo như đối với hàm bình thường, sau đó là ký tự -> và kế đến là thân hàm.

Higher-order functions – Sử dụng lambda function như 1 tham số đầu vào

Một điều tuyệt vời mà Kotlin cho phép ta làm đó là có thể sử dụng hàm như 1 tham số. Đây là điều không thể làm được trong Java. Ta làm điều này như thế nào? Hãy cũng xem ví dụ:

fun printSummary(number1: Int, number2: Int, summaryFunction: (Int, Int) -> Int) {
    val sum = summaryFunction(number1, number2)
    print("Sum of $number1 and $number2 is $sum")
}

Ở đây ta định nghĩa 1 hàm in ra tổng của 2 số nguyên. Hãy nhìn vào các tham số đầu vào của hàm printSummary này. 2 tham số đầu tiên là 2 số nguyên đầu vào để tính tổng, cái này quá dễ. Còn tham số cuối cùng:summaryFunctionHàm thực hiện việc tính tổng của 2 tham số đầu tiên. Hàm printSummary cần 1 tham số đầu vào là 1 hàm có đặc điểm:

  • Nhận 2 tham số Int là tham số đầu vào
  • Trả về kiểu dữ liệu Int

printSummary chỉ đơn thuần sử dụng giá trị trả về của summaryFunction. Giờ ta thử sử dụng hàm printSummary:

printSummary(number1 = 10, number2 = 10, summaryFunction = { a:Int, b:Int ->
        a + b
})
//In ra: Sum of 10 and 10 is 20

Ở đây khi gọi hàm printSummary mình đã truyền giá trị các tham số kèm theo tên để các bạn dễ hiểu. Hãy để ý vào tham số thứ 3, mình đã sử dụng 1 lambda function nhận 2 giá trị đầu vào là ab, và trả về giá trị a+b. Hàm này thỏa mãn điều kiện là nhận 2 tham số Int và trả về kiểu Int.

Lưu ý 1: Khi truyền 1 lambda function vào 1 hàm khác dưới dạng 1 tham số, ta có thể bỏ qua phần định nghĩa kiểu dữ liệu cho các tham số của lambda functionKotlin tự động gán kiểu cho các tham số của lambda function sao cho khớp với lúc khai báo hàm. Ví dụ, ta hoàn toàn có thể gọi hàm printSummary ở trên như sau:

printSummary(number1 = 10, number2 = 10, summaryFunction = { a, b ->
        a + b
})
//In ra: Sum of 10 and 10 is 20

Ta không cần định nghĩa kiểu dữ liệu cho a và b. 2 tham số này sẽ tự động được gán kiểu là Int, bởi Kotlin thông minh, đơn giản là vậy.

Lưu ý 2: Ta cũng không cần phải chỉ ra kiểu dữ liệu trả về cho lambda function, bởi Kotlin cũng sẽ tự động định nghĩa kiểu dữ liệu trả về cho chúng ta (như ở trường hợp trên, kiểu dữ liệu trả về là Int).

Lưu ý 3: Đối với lambda function, khi muốn trả về 1 giá trị, ta không thể dùng từ khóa return. Ta đơn giản chỉ cần nêu ra giá trị đó. Như ví dụ trên là a+b. Tất nhiên giá trị ta nêu ra phải có cùng kiểu dữ liệu trả về của lambda function (như ví dụ trên là Int), nếu không sẽ nhận được thông báo lỗi Type mismatch.

Lưu ý 4: Vì không thể sử dụng từ khóa return, nên ta có thể nêu ra bao nhiêu giá trị tùy thích. Nhưng lambda function sẽ lấy giá trị cuối cùng làm giá trị trả về của hàm:

printSummary(number1 = 10, number2 = 10, summaryFunction = { a, b ->
        a+b
        a-b
        a*b
})
// a*b sẽ là giá trị trả về của hàm summaryFunction
//In ra: Sum of 10 and 10 is 100

Lưu ý 5: Nếu lambda function là tham số cuối cùng của 1 hàm, ta có thể viết nội dung của lambda function bên ngoài cặp (). Ví dụ:

printSummary(number1 = 10, number2 = 10) { a, b ->
        a + b
}
//in ra: Sum of 10 and 10 is 20

Nhìn như này trông code sẽ đẹp hơn và dễ hiểu hơn :).

Tham số it

Khi truyền lambda function vào 1 hàm khác dưới dạng 1 tham số, nếu lambda function đó chỉ có duy nhất 1 tham số, ta có thể bỏ qua việc khai báo tham số đó và sử dụng luôn tham số it. Đây là tham số được tự động generate để sử dụng đối với các lambda function chỉ nhận 1 tham số đầu vào. Ví dụ:

fun printDouble(number: Int, doubleFunction: (Int) -> Int) {
    print(doubleFunction(number))
}

Ta định nghĩa hàm printDouble nhận 1 tham số Int và 1 tham số là lambda function. Lambda function này có duy nhất 1 tham số đầu vào kiểu Int. Bình thường, ta sẽ gọi hàm printDouble như sau:

printDouble(number = 3) { x->
    x* 2
}
//In ra: 6

Tuy nhiên, ta có thể bỏ qua phần khai báo tham số x và sử dụng luôn tham số it (được tự động sinh ra, tương ứng với x):

printDouble(3) {
    it * 2
}
//in ra: 6

Ví dụ khác:

val listPlayer = arrayOf<String>("Ronaldo", "Messi", "Neymar", "Suarez", "Benzema", "Ramos")
val listR = listPlayer.filter {
    it.startsWith("R")
}
print(listR)
//In ra: [Ronaldo, Ramos]

Đoạn code trên in ra các phần tử bắt đầu bởi ký tự R.

Do hàm filter nhận 1 lambda function có đặc điểm: có duy nhất 1 tham số đầu vào kiểu String. Nên ta có thể sử dụng luôn tham số it (được tự động generate, có kiểu String). Thay vì phải viết thế này:

val listPlayer = arrayOf<String>("Ronaldo", "Messi", "Neymar", "Suarez", "Benzema", "Ramos")
val listR = listPlayer.filter {
    playerName->
    playerName.startsWith("R")
}
print(listR)
//In ra: [Ronaldo, Ramos]

Ta không cần tự định nghĩa tham số playerName.

Return trong lambda function

Hãy cùng xem 1 ví dụ. Ở ví dụ sau đây, ta sẽ truyền 1 lambda function vào hàm forEach của 1 intArray. Lambda function này duyệt qua tất cả các phần tử nếu gặp phần tử nào chia hết cho 3, thì hàm lambda này sẽ dừng lại.

fun testReturnFunction() {
    val intList = intArrayOf(1, 3, 5, 7, 9)
    intList.forEach {
        if (it % 3 == 0) {
            return
        }
    }
    println("End of testReturnFunction()")
}

Và gọi thử:

testReturnFunction() //Không có gì xảy ra

Tại sao lại không có gì xảy ra? Bởi câu lệnh return không chỉ kết thúc lambda function, nó còn kết thúc luôn hàm chứa nó là testReturnFunction. Nên câu lệnh println(“End of testReturnFunction()”) không bao giờ được gọi. Để cho Kotlin hiểu rằng, chỉ kết thúc lambda function, ta sửa lại như sau:

fun testReturnFunction() {
    val intList = intArrayOf(1, 3, 5, 7, 9)
    intList.forEach labelForEach@ { // Định nghĩa nhãn labelForEach cho hàm forEach, khi muốn return sẽ dùng đến nhãn này
        if (it % 3 == 0) {
            return@labelForEach // Câu lệnh này chỉ kết thúc hàm có nhãn labelForEach
        }
    }
    println("End of testReturnFunction()")
}

Lúc này, khi gọi hàm testReturnFunction() ta sẽ nhận được dòng log như mong đợi:

End of testRuturnFunction()

Trên đây mình đã trình bày những vấn đề cơ bản mà chúng ta cần phải biết về lambda function. Đây cũng sẽ là loại function mà mình rất hay sử dụng trong phát triển ứng dụng Android. Vì nó rất đơn giản, ngắn gọn, tường minh. Nó thay thế được cho các interface cồng kềnh trong Java (mình sẽ nói trong các bài tiếp theo). Hy vọng các bạn nắm rõ được nó. Sau này ở loạt bài Android, mình sẽ có dịp quay trở lại chủ đề này. Còn giờ thì next qua phần khác thôi 😀

Extension function

Chúng ta đều biết rằng, kiểu dữ liệu String cung cấp hàm cho ta biến 1 chuỗi chữ thường thành chữ hoa:

val normalString = "abcdef"
val upperCaseString = normalString.toUpperCase()
print(upperCaseString) // in ra: ABCDEF

Nhưng giờ nếu ta muốn String có thêm hàm chỉ biến ký tự đầu tiên thành chữ hoa thôi, còn các ký tự khác biến thành chữ thường, thì phải làm thế nào? Có thể bạn sẽ nghĩ đến việc kế thừa lớp String và viết hàm bổ sung theo ý muốn. Nhưng String là lớp final, tức là nó không cho phép kế thừa. Giờ mình muốn mỗi biến String đều được cung cấp hàm như thế này:

Hàm upperFirstLetter hiển thị trong danh sách các hàm gợi ý của String

Kotlin lại cung cấp cho ta 1 tính năng tuyệt vời, đó là extension function. Đây là tính năng cho phép ta mở rộng 1 lớp (trong trường hợp này là String) với các hàm bổ sung mà không cần phải kế thừa lớp đó.

Định nghĩa extension function

Giờ mình sẽ tạo file StringUtils.kt chứa 1 package mới com.duongvu.stringutils, và định nghĩa 1 hàm như sau:

package com.duongvu.stringutils

fun String.upperFirstLetter(): String {
    val firstLetter = this.substring(0, 1).toUpperCase() //Lấy ký tự đầu, viết hoa lên
    return firstLetter.plus(this.substring(1)) // Nối ký tự đầu (đã viết hoa) với phần còn lại của chuỗi.
}

Đây chính là 1 extension function (hàm mở rộng) của kiểu dữ liệu String. Như các bạn thấy, để định nghĩa hàm mở rộng, ta cần phải chỉ ra kiểu dữ liệu (String) trước tên hàm mở rộng (upperFirstLetter). Từ khóa this được sử dụng trong thân hàm biểu diễn cho đối tượng gọi đến hàm upperFirstLetter.

Sử dụng extension function

Sau khi đã định nghĩa xong, để sử dụng extension function thì việc đầu tiên ta phải import package chứa nó. Sau đó ta gọi tới extension function như những hàm bình thường khác của kiểu dữ liệu ta vừa mở rộng (trường hợp này là String):

import com.duongvu.stringutils.upperFirstLetter

fun main(args: Array<String>) {
    val myName = "duong vu"
    print(myName.upperFirstLetter()) // in ra: Duong vu
}

Các bạn có thể thấy rằng, hàm upperFirstLetter đã xuất hiện trong danh sách gợi ý của IDE (như hình mình gửi bên trên).

Tổng kết

Như vậy ở bài viết này mình đã trình bày về:

  • Top level function
  • Lambda function
  • Extension function

Hy vọng qua 2 bài viết về hàm trong Kotlin vừa qua các bạn đã có được kiến thức nền tảng và kỹ năng cơ bản để sử dụng chúng. Tất nhiên vẫn còn 1 số kiến thức mà mình chưa trình bày ra ở đây. Bởi mình thấy sẽ phù hợp, dễ hiểu và bổ ích hơn khi đan xen chúng vào các kiến thức khác, hơn là việc chỉ nêu ra khái niệm. Các kiến thức đó chắc chắn mình sẽ nêu ra trong các bài viết sắp tới. Và bài tiếp theo mình sẽ bắt đầu trình bày về những khái niệm cơ bản nhất của lập trình hướng đối tượng. Đó là class và object. Đây là những khái niệm cực kỳ quan trọng, và sẽ đi theo ta trong mọi dự án. Hãy theo dõi nhé.

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é.

Kotlin Bài 1: Giới thiệu loạt bài hướng dẫn lập trình Kotlin

Chào mọi người, mình là Dương Vũ. Hôm nay mình sẽ viết bài đầu tiên trong loạt bài hướng dẫn lập trình Kotlin, sử dụng trong quá trình phát triển ứng dụng Android. Đây sẽ là loạt bài đầu tiên mình chia sẻ đến những ai yêu thích lập trình di động. Các kiến thức trong đây có thể sẽ mới mẻ, cũng có thể sẽ đơn giản đối với một số người đã trải qua. Vậy hãy cùng mình học tập, giúp đỡ và chia sẻ đến những người có cùng đam mê, sở thích về phát triển ứng dụng nhé. 

[toc]

Tại sao lại có loạt bài này

Mình nhận thấy Kotlin là 1 ngôn ngữ cực kỳ hay và thú vị. Giữa hàng trăm hàng nghìn bài hướng dẫn về lập trình Android ngoài kia, mình chưa tìm ra 1 loạt bài nào giới thiệu bài bản, có lộ trình và dễ hiểu để cho người mới có thể nắm bắt. Có thể có những bài hướng dẫn về lập trình Android sử dụng Kotlin, nhưng rất ít bài hướng dẫn về những điều cốt lõi của ngôn ngữ này. Thế nên mình quyết định tạo ra loạt bài viết Hướng dẫn ngôn ngữ lập trình Kotlin. Loạt bài này sẽ đảm bảo cho bạn những kiến thức cốt lõi, quan trọng nhất để bạn có thể sử dụng trong phát triển ứng dụng Android nói riêng và các nền tảng khác nói chung. Và nó cũng hướng cho bạn cách học và cách đào sâu nghiên cứu, mở rộng cho ngôn ngữ này. So, let’s start!!!

Loạt bài này hướng đến ai

  • Nó hướng đến những người phát triển ứng dụng Android, muốn “chuyển giao công nghệ” từ Java sang Kotlin.
  • Hướng đến những bạn muốn học lập trình Android, mà chưa cả biết Java là gì. Yes!!! Các bạn có thể nhảy ngay vào Kotlin, hoàn toàn không có trở ngại gì cả.
  • Hướng đến … mình. Mình thực sự thích viết, đó là sở thích lớn của mình, chưa kể viết sẽ làm mình trau dồi thêm đc kha khá kiến thức, hehe :))

Nội dung của loạt bài hướng dẫn ngôn ngữ lập trình Kotlin

  • 15 bài hướng dẫn cover toàn bộ kiến thức cốt lõi của ngôn ngữ lập trình Kotlin, giúp bạn có đủ kiến thức để bay vào dự án thực tế 1 cách tự tin và dễ dàng. Vì chỉ có 15 bài thôi nên mỗi  bài sẽ có độ dài tương đối, các bạn hãy chịu khó theo dõi nhé.
  • Do bài viết hướng đến cả những người mới bắt đầu, nên đôi chỗ có thể giải thích hơi kỹ càng quá (đối với những người đã là dev có kinh nghiệm), nên chỗ nào các bạn thấy dễ quá, hay dài vl, thì xin các bạn đừng ném gạch, mà hãy next qua phần tiếp theo :)) Thanks
  • Loạt bài này được viết theo ngôn ngữ… đời sống. Tức là mình sẽ cố diễn giải vấn đề theo cách dân dã nhất, dễ hiểu nhất, chứ không sử dụng nhiều những từ ngữ mang tính học thuật, sẽ rất khó khăn cho ai mới tiếp xúc.
  • Phần sugguest, định hướng để các bạn tìm tòi thêm những kiến thức mới, nâng cao hơn, phục vụ cho dự án sau này.

 

Cách theo dõi loạt bài Kotlin sao cho hiệu quả nhất

Hãy cài IntelliJ IDEA

Như đã nói lúc đầu, bài viết này mình sẽ tập trung vào cốt lõi của ngôn ngữ Kotlin, và chỉ Kotlin mà thôi, không Android, không gì khác. Mình sẽ có riêng 1 loạt bài về hướng dẫn lập trình Android bằng Kotlin để vận dụng kiến thức thu được từ loạt bài này. Vì vậy ở trong những bài hướng dẫn của loạt bài này, mình sẽ không dùng Android Studio để hướng dẫn, mà thay vào đó sẽ dùng IntelliJ IDEA để code các demo. Tại sao? Bởi dùng Android Studio thì mình chỉ có thể tạo các dự án Android. Mà cứ mỗi lần muốn xem kết quả của 1 đoạn code, ta lại phải build nguyên 1 … con app! Rất mất thời gian.

Vì thế để đơn giản hóa mình sẽ sử dụng IntelliJ IDEA. IDE này cho phép ta tạo ra các project Kotlin. Đại khái là ta có thể dùng nó để xem kết quả của các đoạn code nhanh gọn đơn giản hơn, thay vì build nguyên 1 cái app. Các bạn hãy yên tâm vì nếu các bạn đã quen dùng AndroidStudio thì IntelliJ IDEA cũng có cách sử dụng hoàn toàn tương tự, giống đến 90%. Vì AS cũng build từ IntelliJ IDEA mà ra thôi. Nếu các bạn chưa biết cách cài đặt IDE này, hãy tham khảo bài viết Hướng dẫn cài đặt IntelliJ IDEA của mình nhé.

Hãy đọc thật kỹ 

Mình sẽ viết rất kỹ, rất kỹ về từng vấn đề dù là nhỏ nhất. Vì vậy mỗi bài viết có thể khá dài. Tuy nhiên  mình mong các bạn đừng bỏ qua chữ nào, bởi đằng sau mỗi đoạn text miên man đó là các kiến thức cốt lõi được diễn giải ra theo 1 cách dễ hiểu nhất (đối với những người mới). Nếu bạn đọc lướt qua, có thể bạn sẽ bỏ qua mất 1 phần kiến thức sâu hơn, nâng cao hơn mà mình muốn truyền đạt cho các bạn.

Hãy ứng dụng

Mặc dù đây mới chỉ là loạt bài giới thiệu về ngôn ngữ lập trình Kotlin, chúng ta sẽ không xây dựng app trong loạt bài này. Tuy nhiên mình muốn các bạn hãy lập tức ứng dụng những gì học được ngay khi đọc các bài viết. Hãy mở IDE lên và sẵn sàng gõ code. Chỉ có cách code thật nhiều mới có thể giúp chúng ta làm quen với 1 ngôn ngữ mới. Mình cũng xin báo trước rằng mình sẽ sử dụng rất, rất nhiều code để demo. Bởi theo mình nghĩ đó là cách đơn giản nhất giúp các bạn nhanh chóng hiểu được kiến thức. Các bạn cũng hãy tự xây dựng cho mình những đoạn code demo để ứng dụng kiến thức đã học nhé. Sẽ rất hiệu quả đấy!

Hãy chia sẻ

Hãy chia sẻ loạt bài đến những người mới bắt đầu. Những ai còn đang hoang mang giữa vô vàn kiến thức cả tiếng Anh lẫn tiếng Việt ngoài kia. Họ cần 1 lộ trình rõ ràng để làm mục tiêu học tập. Dù đã kiểm tra rất kỹ nhưng có thể ở đâu đó trong các bài viết, mình có sai sót. Hãy đọc và nếu thấy có sơ suất ở đâu, comment cho mình biết để mình cập nhật lại, để nội dung truyền tải được tốt hơn. Hãy cho đi, để được nhận lại.

Bắt đầu

Có thể các bạn sẽ tự hỏi rằng

  • Java ổn rồi ,cần thêm cái thứ Kotlin đó làm chi.
  • Thứ mới ra đó (Kotlin) chắc gì đã ổn định, Java thì best rồi, chả cần thiết phải mò vào Kotlin
  • Tao thích code Java, tao đã quen với nó, tao ghét tất cả mọi thứ khác!
  • Vân vân

Thì đúng, mình không phủ nhận những điều mà các bạn nghĩ ở trong đầu. Nhưng các bạn hãy thử nhìn vào những điểm tốt của Kotlin so với Java, rồi các bạn sẽ thấy, mình có lý do khi viết loạt bài hướng dẫn này.

Sự ngắn gọn

So với Java, Kotlin là vô cùng, vô cùng ngắn gọn. Bạn có thể hình dung, khi mà việc định nghĩa 1 class trong Java có thể tốn đến vài chục dòng hay cả trăm dòng code thì với Kotlin, ta chỉ tốn 1 dòng để làm điều tương tự. Mình sẽ ví dụ ngay đây. Sau đây sẽ là 1 class trong Java:

public class User {
   String name;
   int age;
   String address;
   String phoneNumber;

   public User(String name, int age, String address, String phoneNumber) {
       this.name = name;
       this.age = age;
       this.address = address;
       this.phoneNumber = phoneNumber;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   public String getAddress() {
       return address;
   }

   public void setAddress(String address) {
       this.address = address;
   }

   public String getPhoneNumber() {
       return phoneNumber;
   }

   public void setPhoneNumber(String phoneNumber) {
       this.phoneNumber = phoneNumber;
   }
}

Trên đây là 1 class với 4 thuộc tính, hàm khởi tạo và các phương thức get, set cho các thuộc tính. Trong Kotlin nó sẽ như thế này:

data class User(var name:String,var age: Int,var address:String,var phoneNumber:String)

Vỏn vẹn 1 dòng duy nhất! Đó chính là sự khác biệt về sự ngắn gọn của Kotlin. Đây chỉ là 1 ví dụ. Trong các dự án thực tế ta sẽ còn gặp hàng trăm tình huống mà nếu sử dụng Kotlin, số dòng code phải viết có thể giảm đi đến 90%. Rất tuyệt đúng không. (mình sẽ viết những loạt bài về xây dựng các ứng dụng với quy trình như trong thực tế)

An toàn hơn với các đối tượng null

Như các bạn đã biết thì NullPointerException là 1 lỗi hay gặp nhất ở trong các dự án viết bằng Java. Java cho phép chúng ta gán giá trị null đến 1 đối tượng, nhưng khi ta truy xuất đến đối tượng đó, lỗi NullPointerException sẽ được bắn ra. Mình đã từng làm 1 dự án Android, khi update version mới cho app, mình đã quên không cập nhật đối tượng Java theo config mới trên server. Và kết quả là trong 2 ngày đã có hơn 5000 log crash được báo về hệ thống, tất cả đều là NullPointerException gây ra, bạn có thể hiểu được cảm giác thốn đến thế nào, khi có đến 95% người dùng app bị crash :-s Yeah, và Kotlin được thiết kế để giảm thiểu tối đa sự xuất hiện của cái Exception khốn nạn này 😀 Cực an toàn và tiện lợi, mình sẽ có riêng 1 bài về vấn đề này, các bạn hãy đón chờ nghe.

Hoàn toàn tương thích với Java

Yes! Bạn có thể làm 1 dự án với cả Java và Kotlin cùng 1 lúc. Kotlin được thiết kế để giao tiếp với Java 1 cách hoàn hảo. Bạn có thể sử dụng Java class trong Kotlin và ngược lại, cũng có thể sử dụng Kotlin class trong Java. Tuyệt đúng ko? Điều này có nghĩa là Kotlin có thể sử dụng tất cả các thư viện và framework Java hiện có. Điều này cũng có nghĩa là nếu bạn thích, bạn có thể mở rộng, phát triển dự án Java cũ với Kotlin rất dễ dàng.

Trên đây chỉ là 3 trong số rất nhiều điều hay và thú vị khác của Kotlin mà Java không có. Hãy cùng mình tìm hiểu kỹ hơn trong các bài viết loạt bài này nhé.

Hướng dẫn cài đặt và sử dụng IntelliJ IDEA

Như ở bài giới thiệu về loạt bài Kotlin, mình đã có nói là chúng ta sẽ sử dụng IntelliJ IDEA như 1 IDE chính để code các demo. Hôm nay mình sẽ hướng dẫn các bạn cài đặt và sử dụng IDE này 1 cách cơ bản. Qua bài viết này, các bạn sẽ được trang bị những kỹ năng sử dụng IDE. Những kỹ năng đủ để chúng ta sử dụng trong suốt loạt bài hướng dẫn Kotlin của mình.

[toc]

Cài đặt IntelliJ IDEA

Các bạn download file cài của IntelliJ IDEA tại trang chính thức của Jetbrains. Địa chỉ download:

https://www.jetbrains.com/idea/download/

Giao diện trang download IntelliJ IDEA

Các bạn hãy download bản Community. Đây là bản miễn phí mà nhà phát hành cung cấp cho chúng ta để phát triển các dự án (trong đó có cả Kotlin). Còn bản Ultimate thì là bản cao cấp với nhiều tính năng hơn và phải trả phí. Mà ta thì không cần dùng đến những tính năng cao cấp (ta đang là beginner mà :D) nên không cần quan tâm đến Ultimate làm gì.

 

Sau khi download xong bộ cài các bạn hãy cài đặt như 1 phần mềm thông thường (ấn next – next -…. :D). Đối với các bạn dùng Windows, trong quá trình cài đặt sẽ có 1 bước các bạn cần lưu ý:

chọn .java .kt và download JRE

Ở mục Create Desktop Shortcut, các bạn dùng win bản bao nhiêu bit thì chọn bản tương ứng. Điều quan trọng ở đây là ta cần click chọn vào .java và .kt ở trong mục Create Associations. Như vậy ta mới có thể code được Java và Kotlin trên IDE này. Mình cũng khuyến khích các bạn tick chọn option Download and install JRE x86 by JetBrains. Đây là tùy chọn download JRE (Java Runtime Environment) của JetBrains – nhà phát triển ra IDE này. Điều này sẽ giúp IDE chạy 1 cách ổn định nhất.

Cài xong chạy IDE lên ta sẽ có thế này:

Chọn cấu hình setting cho IDE

Hãy tick chọn Do not import settings để sử dụng cấu hình mặc định của IntelliJ IDEA. Tiếp đó là tới phần chọn theme và các plugin cho IDE. Đây là các bước chỉ cần thực hiện duy nhất khi lần đầu bật IDE:

Chọn theme cho IntelleJ IDEA

Có 2 theme sáng và tối, các bạn tùy chọn theo sở thích. Mình thì mình thích theme sáng hơn. Chọn xong nhấn Next: Default plugins để qua bước tiếp theo.

Tùy chọn disable 1 số plugin

Đây là bước cho phép ta disable 1 số plugin (nếu muốn). Mặc định thì các plugin này đều được bật. Ta có thể Next luôn cho nhanh:

Ấn nút Start using IntelliJ IDEA để bắt đầu

Bước này thực chất là IDE giới thiệu ta 1 số plugin hay ho. Nhưng ta cũng không cần đến chúng nên sẽ nhấn Start using IntelliJ IDEA để bắt đầu làm việc.

Tạo 1 dự án Kotlin mới

Màn hình bắt đầu của IDE

Đây là màn hình đầu tiên mỗi khi chạy IDE, để ta có thể tạo project mới hay mở các project đang làm dở. Giờ ta hãy ấn vào Create New Project để tạo 1 project mới:

Tạo project Kotlin mới

Các bạn chọn như hình: loại Project là Kotlin/JVM.

Chọn đường dẫn JDK

Đến bước này, các bạn điền tên, chọn vị trí lưu trữ source của dự án. Nếu ta chưa chọn đường dẫn JDK, ta sẽ phải trỏ đến đường dẫn thư mục chứa JDK để sử dụng cho dự án. Nếu các bạn chưa cài thì các bạn phải download và cài đặt JDK. Nếu các bạn chưa biết cách cài đặt JDK, hãy tham khảo bài viết của mình. Xong xuôi nhấn Finish để khởi tạo dự án.

Các thao tác cơ bản khi làm việc trong dự án

Tạo file source code

Đây là giao diện làm việc khi khởi tạo xong 1 dự án. Trong loạt bài hướng dẫn về Kotlin này, các file source code mình chủ yếu lưu trong thư mục src (như mình đã trỏ vào ở hình trên). Ta có thể tạo file/class mới bằng cách trỏ vào thư mục src, click chuột phải, và chọn New – New Kotlin File/Class

Tạo file/class mới

Sau đó chọn kiểu File hoặc Class và điền tên cho File (class) mới tạo:

Tạo file mới

Kết quả, ta đã tạo được file Main.kt, nó đã xuất hiện dưới thư mục src:

Tạo file mới thành công

Build dự án

Đầu tiên, để hướng dẫn các bạn cách build dự án, mình sẽ thêm 1 chút code vào file Main.kt mới tạo. Các bạn chưa cần hiểu cấu trúc của đoạn code đó làm gì, việc tìm hiểu đó mình sẽ làm sau. Đơn giản đó là đoạn code hiển thị ra console 1 dòng chữ thôi. Giờ hãy copy code của mình vào file vừa mới tạo:

fun main(args:Array<String>){
    println("Hello world from Kotlin")
}

Yeah, có vẻ nguy hiểm đó. Giờ ta sẽ build và xem kết quả. Nhấn chuột phải vào vùng soạn thảo code rồi chọn Run MainKt:

Hoặc có cách khác nhanh hơn là nhấn tổ hợp phím Ctrl + Shift + F10. Lúc này IDE sẽ build code của chúng ta. Hãy chờ chút rồi xem kết quả.

Lưu ý rằng lần đầu tiên build code có thể sẽ hơi lâu 1 chút, những lần sau sẽ rất nhanh.

Và nhìn xuống vùng console, ta thấy kết quả đã được in ra:

Kết quả hiện ra ở console

Vậy là ta build dự án thành công và hiển thị được dòng chữ Hello world from Kotlin ở console.

Cài đặt phím tắt

Trong quá trình làm việc, sẽ nhanh và tiện lợi hơn nếu ta sử dụng các phím tắt. Để thiết lập phím tắt, Chọn menu File -> Setting, hộp thoại Setting hiện ra, chọn Keymap:

Thiết lập phím tắt

Ở đây các bạn sẽ tha hồ tìm hiểu và thiết lập các phím tắt riêng phù hợp với sở thích của mình. Tuy nhiên cũng có thể thiết lập theo 1 số mẫu có sẵn. Các bạn nhìn lên mục Keymap, chọn menu sổ xuống sẽ hiện ra rất nhiều tùy chọn cho chúng ta. Ta có thể thiết lập phím tắt giống như của bên Eclipse, JBuilder, Netbean, Visual Studio,…

Rất rất nhiều. Nếu bạn đã quen với 1 IDE nào đó trước, hãy sử dụng luôn bộ phím tắt của IDE đó luôn cho nhanh. Mình thì dùng của Eclipse.

Kết luận

Việc cài đặt và sử dụng IntelliJ IDEA khá đơn giản và dễ dàng, nhất là đối với các bạn đã làm qua với Android Studio. Vì thực chất Android Studio là 1 bản build riêng biệt từ IntelliJ IDEA. Thậm chí ta có thể cài đặt plugin Android vào IntelliJ IDEA để code Android mà không cần tới Android Studio. Nói như vậy để các bạn hiểu, cách sử dụng 2 IDE này là hoàn toàn tương tự. Hy vọng qua bài viết này các bạn đã biết được cách cài đặt và sử dụng cơ bản IntelliJ IDEA để phục vụ cho các bài học Kotlin mà mình sẽ cung cấp. Cảm ơn đã theo dõi, hẹn gặp lại 😉