Bỏ qua để đến nội dung

📦 Biến - val và var

🎯 Mục tiêu: Hiểu sự khác biệt giữa valvar, và tại sao Kotlin khuyến khích immutability (tính bất biến).


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

val - Biến không thay đổi (Immutable reference)

Phần tiêu đề “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 reassigned

var - Biến có thể thay đổi (Mutable reference)

Phần tiêu đề “var - 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

Phần tiêu đề “🔍 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

⚠️ val không có nghĩa là object immutable!

Phần tiêu đề “⚠️ 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]

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()

Đô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")
}
}
}

Để 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!

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ường

Quy tắc cơ bản:

  1. Bắt đầu bằng chữ cái hoặc _
  2. Không bắt đầu bằng số
  3. Không dùng keyword của Kotlin (val, var, class, fun…)
  4. Phân biệt hoa/thường (nameName)

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
}

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")
}
}
}

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ần var
  • Hiểu rằng val không đảm bảo object immutable
  • Sử dụng lateinit var cho khởi tạo muộn
  • Phân biệt valconst val
  • Đặt tên biến đúng quy ước camelCase

Tiếp theo: Giá trị và Kiểu dữ liệu