Biến trong Kotlin: val và var
🎯 Mục tiêu: Hiểu sự khác biệt giữa
valvàvar, và tại sao Kotlin khuyến khích immutability (tính bất biến).
💡 Khái niệm
Hãy tưởng tượng bạn có hai loại hộp:
| Loại hộp | Keyword | Đặc điểm |
|---|---|---|
| 🔒 Hộp khóa | val | Sau khi bỏ đồ vào, không thể thay đổi |
| 📦 Hộp mở | var | Có thể thay đồ bất cứ lúc nào |
Kotlin mặc định muốn bạn dùng “hộp khóa” (val) vì an toàn hơn!
val name = "Bumbii" // 🔒 Không thể thay đổi
var score = 100 // 📦 Có thể thay đổi
// name = "Other" // ❌ Lỗi compile!
score = 150 // ✅ OK📝 Cú pháp khai báo biến
val - Biến không thay đổi (Immutable reference)
val pi = 3.14159
val appName = "Bumbii Academy"
val isActive = true
// Không thể gán lại giá trị
// pi = 3.14 // ❌ Compile error: Val cannot be reassignedvar - Biến có thể thay đổi (Mutable reference)
var count = 0
count = 1 // ✅ OK
count = 2 // ✅ OK
var userName = "Alice"
userName = "Bob" // ✅ OK🔍 Type Inference - Kotlin tự suy luận kiểu
Kotlin thông minh! Bạn không cần khai báo kiểu dữ liệu, Kotlin sẽ tự suy luận:
// Kotlin tự hiểu kiểu dữ liệu
val name = "Kotlin" // String
val age = 25 // Int
val height = 1.75 // Double
val isStudent = true // Boolean
// Hoặc khai báo tường minh (explicit)
val name: String = "Kotlin"
val age: Int = 25
val height: Double = 1.75
val isStudent: Boolean = true[!TIP] Best Practice: Để Kotlin tự suy luận kiểu khi giá trị đã rõ ràng. Chỉ khai báo tường minh khi cần thiết hoặc để code dễ đọc hơn.
⚠️ val không có nghĩa là object immutable!
Đây là điểm hay bị hiểu nhầm:
val numbers = mutableListOf(1, 2, 3)
// Không thể gán list mới
// numbers = mutableListOf(4, 5, 6) // ❌ Compile error
// NHƯNG có thể thay đổi NỘI DUNG của list
numbers.add(4) // ✅ OK - giờ là [1, 2, 3, 4]
numbers[0] = 100 // ✅ OK - giờ là [100, 2, 3, 4][!WARNING]
valchỉ đảm bảo reference không thay đổi, không phải nội dung bên trong object.
Nếu muốn list thực sự immutable:
val numbers = listOf(1, 2, 3) // Immutable list
// numbers.add(4) // ❌ Không có method add()🔧 Late Initialization với lateinit
Đôi khi bạn không thể khởi tạo biến ngay lập tức:
class UserProfile {
// Sẽ được khởi tạo sau (ví dụ: sau khi load từ database)
lateinit var userName: String
fun initializeUser() {
userName = "Bumbii"
}
fun printUser() {
// Kiểm tra đã khởi tạo chưa
if (::userName.isInitialized) {
println("User: $userName")
} else {
println("User chưa được khởi tạo")
}
}
}[!CAUTION]
lateinitchỉ dùng với:
var(không dùng vớival)- Non-primitive types (không dùng với
Int,Double,Boolean…)- Thuộc tính trong class (không dùng ở top-level hoặc local)
📌 Hằng số với const val
Để khai báo hằng số compile-time:
// Top-level hoặc trong object
const val PI = 3.14159
const val MAX_USERS = 1000
const val APP_NAME = "Bumbii Academy"
object Config {
const val API_URL = "https://api.bumbii.com"
const val TIMEOUT = 30
}| Keyword | Khi nào dùng |
|---|---|
val | Giá trị không đổi trong runtime |
const val | Giá trị biết trước lúc compile (primitives + String) |
val currentTime = System.currentTimeMillis() // ✅ Runtime value
// const val currentTime = System.currentTimeMillis() // ❌ Lỗi!📏 Quy tắc đặt tên biến
Kotlin sử dụng camelCase:
// ✅ Đúng
val firstName = "John"
val totalAmount = 1000
val isUserLoggedIn = true
// ✅ Hằng số dùng SCREAMING_SNAKE_CASE
const val MAX_RETRY_COUNT = 3
const val DATABASE_NAME = "bumbii_db"
// ❌ Sai
val first_name = "John" // underscore không đúng quy ước
val TOTAL = 1000 // ALL CAPS cho biến thườngQuy tắc cơ bản:
- Bắt đầu bằng chữ cái hoặc
_ - Không bắt đầu bằng số
- Không dùng keyword của Kotlin (
val,var,class,fun…) - Phân biệt hoa/thường (
name≠Name)
🛠️ Thực hành
Bài tập 1: Phân biệt val và var
fun main() {
// TODO: Khai báo biến cho thông tin sinh viên
// - Mã sinh viên (không đổi)
// - Tên sinh viên (không đổi)
// - Điểm trung bình (có thể thay đổi)
// - Số tín chỉ đã học (có thể tăng)
}Lời giải:
fun main() {
val studentId = "SV001" // 🔒 Mã SV không đổi
val studentName = "Nguyễn Văn A" // 🔒 Tên không đổi
var gpa = 3.5 // 📦 Điểm có thể cập nhật
var totalCredits = 30 // 📦 Tín chỉ tăng dần
println("=== THÔNG TIN SINH VIÊN ===")
println("Mã SV: $studentId")
println("Họ tên: $studentName")
println("GPA: $gpa")
println("Tín chỉ: $totalCredits")
// Cập nhật sau kỳ học mới
gpa = 3.7
totalCredits = 45
println("\n=== SAU KỲ HỌC MỚI ===")
println("GPA: $gpa")
println("Tín chỉ: $totalCredits")
}Bài tập 2: Sửa lỗi trong code sau
fun main() {
val userName = "Alice"
userName = "Bob" // ???
const val MAX = 100 // ???
lateinit val message: String // ???
lateinit var count: Int // ???
}Lời giải:
// Top-level hoặc trong object
const val MAX = 100
fun main() {
var userName = "Alice" // Đổi val → var nếu cần thay đổi
userName = "Bob" // ✅ OK
lateinit var message: String // lateinit chỉ dùng với var
message = "Hello"
var count: Int = 0 // lateinit không dùng với primitive
}📱 Trong Android / Jetpack Compose
class MainActivity : AppCompatActivity() {
// lateinit thường dùng cho Views
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}Jetpack Compose:
@Composable
fun Counter() {
// Sử dụng remember và mutableStateOf
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Tăng")
}
}
}✅ Checklist - Tự kiểm tra
Sau bài học này, bạn có thể:
- Hiểu sự khác biệt giữa
val(immutable reference) vàvar(mutable reference) - Biết khi nào nên dùng
val(mặc định) và khi nào cầnvar - Hiểu rằng
valkhông đảm bảo object immutable - Sử dụng
lateinit varcho khởi tạo muộn - Phân biệt
valvàconst val - Đặt tên biến đúng quy ước camelCase
Tiếp theo: Giá trị và Kiểu dữ liệu