Chào mừng bạn quay trở lại với loạt bài viết “Android Developer Roadmap”! Sau khi chúng ta đã cùng nhau tìm hiểu về Lộ trình học Lập trình viên Android và cân nhắc nên chọn Kotlin hay Java cho Android vào năm 2025, cùng với việc thiết lập môi trường phát triển, giờ là lúc chúng ta đi sâu vào “linh hồn” của phát triển Android hiện đại: ngôn ngữ Kotlin. Nếu bạn đã quyết định theo đuổi Kotlin (một lựa chọn tuyệt vời!), bài viết này sẽ là điểm khởi đầu lý tưởng, giúp bạn làm quen với những cú pháp và khái niệm cốt lõi nhất.
Kotlin không chỉ là một “phiên bản tốt hơn” của Java; nó mang đến một triết lý lập trình mới, tập trung vào sự ngắn gọn, an toàn và hiệu quả. Đối với một lập trình viên Android, việc nắm vững Kotlin là chìa khóa để xây dựng các ứng dụng mạnh mẽ, dễ bảo trì và tận dụng tối đa các tính năng của nền tảng.
Hãy cùng khám phá những viên gạch đầu tiên xây dựng nên thế giới Kotlin!
Mục lục
Tại Sao Kotlin Lại Phổ Biến Đến Vậy Trên Android?
Trước khi đi vào chi tiết cú pháp, việc hiểu lý do đằng sau sự phổ biến của Kotlin sẽ tạo động lực lớn cho bạn. Kotlin là ngôn ngữ chính thức được Google hỗ trợ cho phát triển Android kể từ năm 2017. Sự bùng nổ của nó đến từ những lợi ích rõ rệt:
- Ngắn Gọn (Concise): Viết ít code hơn để làm cùng một việc so với Java. Điều này giúp tăng tốc độ phát triển và giảm khả năng mắc lỗi.
- An Toàn Kiểu Dữ Liệu Rỗng (Null Safety): Đây là một trong những tính năng được yêu thích nhất, giúp giảm thiểu đáng kể các lỗi NullPointerException (NPE) khét tiếng, vốn là “cơn ác mộng” của nhiều lập trình viên Java.
- Tương Tác Hoàn Hảo Với Java (Interoperable): Bạn có thể dễ dàng sử dụng code Kotlin trong dự án Java và ngược lại. Điều này rất quan trọng khi làm việc với các dự án Android cũ hoặc sử dụng các thư viện Java sẵn có.
- Hiệu Năng Tốt: Code Kotlin thường biên dịch ra bytecode tương đương hoặc tốt hơn Java, đảm bảo hiệu năng ứng dụng không bị suy giảm.
- Công Cụ Hỗ Trợ Tuyệt Vời: Android Studio được tối ưu hóa để làm việc với Kotlin, cung cấp các tính năng như gợi ý code, refactoring, và kiểm tra lỗi mạnh mẽ.
Với những lợi ích này, không có gì ngạc nhiên khi Kotlin đã trở thành lựa chọn hàng đầu cho các dự án Android mới.
Những Cú Pháp Cơ Bản Đầu Tiên
Hãy bắt đầu với những thành phần nhỏ nhất: biến, kiểu dữ liệu và hàm.
Biến (Variables)
Trong Kotlin, bạn khai báo biến bằng từ khóa `val` hoặc `var`:
val
(viết tắt của value): Dùng cho các biến chỉ đọc (read-only), không thể gán lại giá trị sau khi khởi tạo. Tương tự như `final` trong Java.var
(viết tắt của variable): Dùng cho các biến có thể thay đổi giá trị (mutable).
fun main() {
val ten: String = "Alice" // Khai báo biến chỉ đọc 'ten' với kiểu String
var tuoi: Int = 30 // Khai báo biến có thể thay đổi 'tuoi' với kiểu Int
println("Tên: $ten")
println("Tuổi ban đầu: $tuoi")
tuoi = 31 // Có thể gán lại giá trị cho biến 'var'
println("Tuổi sau khi thay đổi: $tuoi")
// ten = "Bob" // Lỗi biên dịch: Val cannot be reassigned
}
Kotlin thường có thể suy luận kiểu dữ liệu của biến dựa trên giá trị khởi tạo, nên bạn không nhất thiết phải khai báo tường minh kiểu dữ liệu (như : String
hoặc : Int
) trừ khi cần thiết:
val ten = "Alice" // Kotlin tự hiểu 'ten' là String
var tuoi = 30 // Kotlin tự hiểu 'tuoi' là Int
Tuy nhiên, việc khai báo tường minh kiểu dữ liệu có thể giúp code dễ đọc hơn trong một số trường hợp.
Kiểu Dữ Liệu (Data Types)
Không giống Java có kiểu dữ liệu nguyên thủy (primitive types) và kiểu wrapper, trong Kotlin, mọi thứ đều là đối tượng (objects). Các kiểu dữ liệu cơ bản bao gồm:
- Số:
Byte
,Short
,Int
,Long
,Float
,Double
- Ký tự:
Char
- Boolean:
Boolean
(true
hoặcfalse
) - Chuỗi:
String
Các kiểu này cung cấp các hàm và thuộc tính hữu ích. Ví dụ:
val soNguyen: Int = 100
val soThuc: Double = 12.34
val kyTu: Char = 'A'
val laDung: Boolean = true
val chuoiXinChao: String = "Xin chào Kotlin"
println("Số nguyên: $soNguyen")
println("Số thực: $soThuc")
println("Ký tự: $kyTu")
println("Boolean: $laDung")
println("Độ dài chuỗi: ${chuoiXinChao.length}") // Truy cập thuộc tính length
Lưu ý cú pháp “string template” ($variable
hoặc ${expression}
) bên trong chuỗi, giúp nhúng giá trị biến hoặc kết quả biểu thức một cách gọn gàng.
Hàm (Functions)
Bạn khai báo hàm bằng từ khóa fun
. Cú pháp bao gồm tên hàm, danh sách tham số (với tên và kiểu dữ liệu), và kiểu dữ liệu trả về (nếu có):
fun congHaiSo(a: Int, b: Int): Int { // Hàm nhận 2 Int, trả về 1 Int
return a + b
}
fun inLoiChao(ten: String) { // Hàm nhận 1 String, không trả về gì (kiểu trả về là Unit - có thể bỏ qua)
println("Chào mừng, $ten!")
}
fun main() {
val tong = congHaiSo(5, 7)
println("Tổng của 5 và 7 là: $tong") // Output: Tổng của 5 và 7 là: 12
inLoiChao("Developers") // Output: Chào mừng, Developers!
}
Đối với các hàm đơn giản chỉ có một biểu thức, bạn có thể sử dụng cú pháp “single-expression functions”:
fun nhanHaiSo(a: Int, b: Int): Int = a * b
// Hoặc thậm chí bỏ qua kiểu trả về nếu Kotlin có thể suy luận
fun truHaiSo(a: Int, b: Int) = a - b
fun main() {
println("Tích của 3 và 4 là: ${nhanHaiSo(3, 4)}") // Output: Tích của 3 và 4 là: 12
println("Hiệu của 10 và 3 là: ${truHaiSo(10, 3)}") // Output: Hiệu của 10 và 3 là: 7
}
An Toàn Kiểu Dữ Liệu Rỗng (Null Safety)
Đây là tính năng nổi bật nhất của Kotlin. Mặc định, các biến trong Kotlin là không thể rỗng (non-nullable). Điều này có nghĩa là bạn không thể gán giá trị null
cho một biến thông thường:
var chuoi: String = "Hello"
// chuoi = null // Lỗi biên dịch: Null can not be a value of a non-null type String
Để cho phép một biến có thể chứa giá trị null
, bạn cần thêm dấu ?
vào sau kiểu dữ liệu:
var chuoiCoTheRong: String? = "Hello"
chuoiCoTheRong = null // Hợp lệ
Khi làm việc với biến có thể rỗng (nullable variable), Kotlin yêu cầu bạn phải xử lý trường hợp nó là null
trước khi truy cập các thuộc tính hoặc gọi hàm của nó. Nếu không, lỗi biên dịch sẽ xuất hiện. Có vài cách để xử lý:
- Kiểm tra null tường minh: Sử dụng câu lệnh
if
. - Safe Call Operator (
?.
): An toàn nhất. Chỉ thực hiện hành động nếu biến không phải lànull
. Nếu biến lànull
, toàn bộ biểu thức trả vềnull
. - Elvis Operator (
?:
): Cung cấp một giá trị mặc định nếu biểu thức bên trái lànull
. - Non-null Asserted Call (
!!
): “Ép buộc” Kotlin coi biến này là không rỗng. Cực kỳ cẩn thận khi sử dụng! Nếu biến đó thực sự lànull
khi bạn dùng!!
, chương trình sẽ ném raNullPointerException
giống như Java.
var tenCoTheRong: String? = "Kotlin"
// 1. Kiểm tra null tường minh
if (tenCoTheRong != null) {
println("Độ dài tên: ${tenCoTheRong.length}") // An toàn
} else {
println("Tên bị rỗng.")
}
// 2. Safe Call Operator (?. )
println("Độ dài tên (Safe Call): ${tenCoTheRong?.length}") // Nếu tenCoTheRong là null, kết quả là null
// 3. Elvis Operator (?:)
val doDaiAnToan = tenCoTheRong?.length ?: 0 // Nếu tenCoTheRong?.length là null, dùng 0
println("Độ dài an toàn: $doDaiAnToan")
// 4. Non-null Asserted Call (!! ) - Dùng cẩn thận!
// val doDaiKhongAnToan = tenCoTheRong!!.length // Nếu tenCoTheRong là null, sẽ crash với NPE!
Việc sử dụng ?.
và ?:
là cách tiếp cận “Kotlin-idiomatic” để xử lý null, giúp code sạch sẽ và an toàn hơn rất nhiều so với việc kiểm tra if (x != null)
liên tục.
Lớp (Classes) và Đối Tượng (Objects)
Kotlin là ngôn ngữ hướng đối tượng. Bạn khai báo lớp bằng từ khóa class
:
class Nguoi {
// Thuộc tính (Properties)
var ten: String = "Chưa đặt tên"
var tuoi: Int = 0
// Hàm (Methods)
fun xinChao() {
println("Xin chào, tôi là $ten, $tuổi tuổi.")
}
}
fun main() {
val nguoi1 = Nguoi() // Tạo một đối tượng từ lớp Nguoi
nguoi1.ten = "Alice"
nguoi1.tuoi = 30
nguoi1.xinChao() // Output: Xin chào, tôi là Alice, 30 tuổi.
val nguoi2 = Nguoi()
nguoi2.xinChao() // Output: Xin chào, tôi là Chưa đặt tên, 0 tuổi.
}
Hàm Khởi Tạo (Constructors)
Kotlin có hàm khởi tạo chính (primary constructor) và có thể có nhiều hàm khởi tạo phụ (secondary constructors). Hàm khởi tạo chính là một phần của khai báo lớp:
class SinhVien(val maSinhVien: String, var ten: String, var tuoi: Int = 18) { // Hàm khởi tạo chính
// Khối init: Chạy khi đối tượng được tạo
init {
println("Đối tượng SinhVien được tạo với mã: $maSinhVien")
}
fun inThongTin() {
println("SV: $maSinhVien, Tên: $ten, Tuổi: $tuoi")
}
}
fun main() {
val sv1 = SinhVien("A101", "Nam", 20) // Sử dụng hàm khởi tạo chính
sv1.inThongTin() // Output: SV: A101, Tên: Nam, Tuổi: 20
val sv2 = SinhVien("B202", "Nu") // Sử dụng giá trị mặc định cho tuoi
sv2.inThongTin() // Output: SV: B202, Tên: Nu, Tuổi: 18
}
Data Classes
Các lớp chỉ dùng để lưu trữ dữ liệu rất phổ biến trong Android (ví dụ: biểu diễn một User, một Product). Kotlin cung cấp data class
giúp tự động tạo các hàm hữu ích như equals()
, hashCode()
, toString()
, copy()
và các hàm componentN()
:
data class SanPham(val id: Int, val ten: String, var gia: Double)
fun main() {
val sp1 = SanPham(1, "Điện Thoại", 1000.0)
val sp2 = SanPham(1, "Điện Thoại", 1000.0) // Cùng dữ liệu với sp1
val sp3 = SanPham(2, "Máy Tính Bảng", 500.0)
println(sp1.toString()) // Output: SanPham(id=1, ten=Điện Thoại, gia=1000.0) - toString() tự động
println("sp1 == sp2: ${sp1 == sp2}") // Output: sp1 == sp2: true - equals() tự động
println("sp1 == sp3: ${sp1 == sp3}") // Output: sp1 == sp3: false
val sp1Copy = sp1.copy(gia = 950.0) // Tạo bản sao, thay đổi giá
println(sp1Copy) // Output: SanPham(id=1, ten=Điện Thoại, gia=950.0)
}
data class
giúp giảm đáng kể lượng code lặp đi lặp lại khi tạo các lớp mô hình dữ liệu.
Cấu Trúc Điều Khiển (Control Flow)
Kotlin hỗ trợ các cấu trúc điều khiển quen thuộc như if
, when
, for
, while
.
If as an Expression
Trong Kotlin, if
không chỉ là một câu lệnh mà còn là một biểu thức, nghĩa là nó có thể trả về một giá trị:
fun main() {
val so = 10
val ketQua = if (so > 0) {
"Số dương"
} else if (so < 0) {
"Số âm"
} else {
"Số không"
}
println("Số $so là: $ketQua") // Output: Số 10 là: Số dương
}
When Expression
when
là một sự thay thế linh hoạt hơn cho switch
trong Java. Nó cũng có thể là một biểu thức:
fun moTaSo(so: Int): String {
return when (so) {
1 -> "Một"
2 -> "Hai"
in 3..10 -> "Từ ba đến mười" // Kiểm tra trong khoảng
is Int -> "Một số nguyên bất kỳ khác" // Kiểm tra kiểu dữ liệu
else -> "Không xác định" // Mệnh đề else là bắt buộc nếu when được dùng như một biểu thức
}
}
fun main() {
println(moTaSo(5)) // Output: Từ ba đến mười
println(moTaSo(1)) // Output: Một
println(moTaSo(99)) // Output: Một số nguyên bất kỳ khác
}
Vòng Lặp (Loops)
Kotlin có for
, while
, và do-while
.
// Vòng lặp for với range
for (i in 1..5) { // Duyệt từ 1 đến 5 (bao gồm cả 5)
print("$i ") // Output: 1 2 3 4 5
}
println()
for (i in 5 downTo 1) { // Duyệt từ 5 về 1
print("$i ") // Output: 5 4 3 2 1
}
println()
for (i in 1..10 step 2) { // Duyệt từ 1 đến 10, bước nhảy 2
print("$i ") // Output: 1 3 5 7 9
}
println()
// Vòng lặp for với collection (sẽ nói thêm về collection sau)
val danhSachTraiCay = listOf("Táo", "Chuối", "Cam")
for (traiCay in danhSachTraiCay) {
println("Tôi thích $traiCay")
}
// Vòng lặp while
var dem = 0
while (dem < 3) {
println("Đếm: $dem")
dem++
}
// Vòng lặp do-while
var x = 0
do {
println("Do-While: $x")
x++
} while (x < 3)
Hàm Mở Rộng (Extension Functions)
Hàm mở rộng cho phép bạn “thêm” hàm mới vào một lớp đã tồn tại mà không cần kế thừa lớp đó hoặc sử dụng các mẫu thiết kế như Decorator. Điều này cực kỳ hữu ích trong Android để thêm các hàm tiện ích cho các lớp của Android SDK hoặc các thư viện khác.
// Định nghĩa một hàm mở rộng cho lớp String
fun String.vietHoaKyTuDau(): String {
return if (isNotEmpty()) {
this[0].uppercase() + substring(1) // 'this' tham chiếu đến đối tượng String
} else {
this
}
}
fun main() {
val chuoiGoc = "hello kotlin"
val chuoiMoi = chuoiGoc.vietHoaKyTuDau() // Gọi hàm mở rộng như hàm thông thường
println(chuoiMoi) // Output: Hello kotlin
}
Trong Android, bạn thường thấy các hàm mở rộng được dùng để đơn giản hóa việc hiển thị Toast, làm việc với SharedPreferences, hoặc xử lý View.
Lambdas và Higher-Order Functions
Kotlin coi hàm là “first-class citizens”, nghĩa là bạn có thể truyền hàm như tham số, trả về hàm, hoặc lưu trữ hàm trong biến. Hàm ẩn danh (lambda expressions) là các hàm không có tên, rất hữu ích khi bạn cần truyền một khối code ngắn gọn làm tham số.
// Lambda expression: (Int) -> Int
val nhanDoi: (Int) -> Int = { so -> so * 2 }
val congThemNam: (Int) -> Int = { it + 5 } // 'it' là tên mặc định cho tham số duy nhất
// Higher-order function: Hàm nhận một hàm khác làm tham số
fun apDungHanhDongChoSo(so: Int, hanhDong: (Int) -> Int): Int {
return hanhDong(so)
}
fun main() {
val ketQua1 = apDungHanhDongChoSo(10, nhanDoi) // Truyền lambda vào hàm
println("Nhân đôi 10: $ketQua1") // Output: Nhân đôi 10: 20
val ketQua2 = apDungHanhDongChoSo(10, congThemNam)
println("Cộng thêm 5 vào 10: $ketQua2") // Output: Cộng thêm 5 vào 10: 15
// Truyền lambda trực tiếp (trailing lambda syntax)
val ketQua3 = apDungHanhDongChoSo(10) { it / 2 }
println("Chia đôi 10: $ketQua3") // Output: Chia đôi 10: 5
}
Bạn sẽ gặp rất nhiều lambdas trong code Android hiện đại, đặc biệt khi làm việc với các API yêu cầu callback hoặc listener (ví dụ: xử lý sự kiện click cho Button).
Collections (Bộ Sưu Tập)
Kotlin cung cấp các lớp dễ sử dụng cho danh sách (List), tập hợp (Set) và bản đồ (Map). Quan trọng là Kotlin phân biệt giữa các bộ sưu tập không thể thay đổi (immutable) và có thể thay đổi (mutable).
// Danh sách không thể thay đổi
val danhSachChiDoc = listOf("Apple", "Banana", "Orange")
// danhSachChiDoc.add("Grape") // Lỗi biên dịch: Immutable list
// Danh sách có thể thay đổi
val danhSachCoTheThayDoi = mutableListOf("Apple", "Banana")
danhSachCoTheThayDoi.add("Orange")
danhSachCoTheThayDoi.removeAt(0) // Xóa "Apple"
println("Danh sách hiện tại: $danhSachCoTheThayDoi") // Output: Danh sách hiện tại: [Banana, Orange]
// Map không thể thay đổi
val mapChiDoc = mapOf("key1" to "value1", "key2" to "value2")
// mapChiDoc["key3"] = "value3" // Lỗi biên dịch
// Map có thể thay đổi
val mapCoTheThayDoi = mutableMapOf("key1" to "value1")
mapCoTheThayDoi["key2"] = "value2"
println("Map hiện tại: $mapCoTheThayDoi") // Output: Map hiện tại: {key1=value1, key2=value2}
// Set không thể thay đổi
val setChiDoc = setOf(1, 2, 3, 2) // Phần tử trùng lặp bị loại bỏ
println("Set: $setChiDoc") // Output: Set: [1, 2, 3]
Làm quen với các hàm tiện ích trên collections như filter
, map
, forEach
, find
, sorted
… là rất quan trọng để viết code xử lý dữ liệu hiệu quả.
Scoped Functions (Hàm Phạm Vi)
Các hàm như let
, run
, apply
, also
là những công cụ mạnh mẽ giúp thực thi một khối code trong ngữ cảnh của một đối tượng. Chúng giúp làm cho code trở nên ngắn gọn và dễ đọc hơn, đặc biệt khi làm việc với các đối tượng có thể rỗng hoặc khi cần cấu hình một đối tượng.
let
: Thực thi khối lambda nếu đối tượng không phải lànull
. Thường dùng với Safe Call (?.let {}
).run
: Giốnglet
(thực thi trên đối tượng không null), nhưng bên trong lambda,this
tham chiếu đến đối tượng đó. Thường dùng để thực thi một khối code trên đối tượng.apply
: Thực thi khối lambda trên đối tượng, và trả về chính đối tượng đó. Thường dùng để cấu hình thuộc tính của đối tượng.also
: Giốnglet
(truy cập đối tượng bằngit
), nhưng trả về chính đối tượng đó. Thường dùng để thực hiện hành động phụ (side effect) như logging hoặc kiểm tra dữ liệu.
data class ThietBi(var ten: String, var serial: String? = null)
fun main() {
val thietBi: ThietBi? = ThietBi("Laptop")
thietBi?.serial = "SN12345"
// Sử dụng let để xử lý nếu thietBi không null
thietBi?.let {
// 'it' là đối tượng ThietBi không null
println("Thiết bị không rỗng, tên: ${it.ten}")
println("Serial: ${it.serial}")
}
// Sử dụng apply để cấu hình đối tượng
val dienThoai = ThietBi("Smartphone").apply {
// 'this' là đối tượng ThietBi
serial = "ABC987"
// Có thể gọi các hàm hoặc đặt thuộc tính khác ở đây
}
println("Điện thoại: ${dienThoai.ten}, Serial: ${dienThoai.serial}")
// Sử dụng also để thực hiện hành động phụ
val mayTinhBang = ThietBi("Tablet").also {
// 'it' là đối tượng ThietBi
println("--- Đang xử lý Máy Tính Bảng ---")
// Có thể log hoặc kiểm tra ở đây
}
println("Đã xử lý xong Máy Tính Bảng")
}
Các hàm phạm vi là một ví dụ tuyệt vời về cách Kotlin tận dụng lambdas để viết code ngắn gọn và biểu cảm.
Tóm Tắt Các Khái Niệm Cốt Lõi
Dưới đây là bảng tổng hợp nhanh các khái niệm quan trọng mà chúng ta vừa tìm hiểu:
Khái Niệm | Cú Pháp / Từ Khóa | Mô Tả |
---|---|---|
Khai báo Biến | val , var |
val (chỉ đọc), var (có thể thay đổi). Kotlin suy luận kiểu dữ liệu. |
Kiểu Dữ Liệu Rỗng | ? , ?. , ?: , !! |
? : cho phép null. ?. : Safe call. ?: : Elvis operator. !! : Non-null assertion (dùng cẩn thận). |
Khai báo Hàm | fun |
Sử dụng từ khóa fun . Có thể có kiểu trả về tường minh hoặc suy luận. |
Lớp Dữ Liệu | data class |
Lớp dùng để lưu trữ dữ liệu, tự động tạo các hàm như equals , hashCode , toString . |
Cấu Trúc Điều Khiển | if , when , for , while |
if /when có thể dùng như biểu thức. when mạnh hơn switch . Vòng lặp quen thuộc. |
Hàm Mở Rộng | fun TênLớp.tênHàm() |
Thêm hàm vào lớp đã tồn tại mà không cần kế thừa. |
Lambdas & Higher-Order Functions | {} , (Kiểu) -> Kiểu |
Hàm là first-class citizens. Lambdas giúp viết code ngắn gọn cho các callback, listener. |
Collections | listOf , mutableListOf , mapOf , mutableMapOf , setOf , mutableSetOf |
Phân biệt immutable/mutable collections. Nhiều hàm tiện ích. |
Scoped Functions | let , run , apply , also |
Thực thi khối code trong ngữ cảnh của đối tượng, giúp code gọn gàng, đặc biệt với nullables. |
Lời Kết
Bài viết này chỉ là bước khởi đầu trên con đường chinh phục Kotlin. Chúng ta đã cùng nhau điểm qua những cú pháp và khái niệm cốt lõi nhất, từ biến, hàm cơ bản đến những tính năng mạnh mẽ như Null Safety, Data Classes, Extension Functions, Lambdas và Scoped Functions. Đây là những nền tảng vững chắc giúp bạn bắt đầu xây dựng các ứng dụng Android bằng Kotlin.
Điều quan trọng nhất bây giờ là thực hành! Hãy mở Android Studio, tạo một dự án mới và thử nghiệm những cú pháp bạn vừa học. Hãy viết code, sửa lỗi, và dần làm quen với “cảm giác” khi code bằng Kotlin. Cộng đồng Kotlin rất lớn và thân thiện, đừng ngần ngại tìm kiếm tài liệu hoặc hỏi khi gặp khó khăn.
Trong các bài viết tiếp theo của series “Android Developer Roadmap”, chúng ta sẽ bắt đầu áp dụng những kiến thức Kotlin này vào việc xây dựng giao diện người dùng (UI), quản lý trạng thái, xử lý dữ liệu và nhiều thành phần khác của một ứng dụng Android thực tế.
Chúc bạn học tốt và hẹn gặp lại trong các bài viết tiếp theo!