Constructor trong Kotlin
🎯 Mục tiêu: Hiểu Primary Constructor, Secondary Constructor, và init blocks - cách khởi tạo object trong Kotlin.
💡 Constructor là gì?
Constructor là hàm đặc biệt được gọi khi tạo object. Kotlin có hai loại:
- Primary Constructor: Khai báo ngay sau tên class
- Secondary Constructor: Khai báo trong body class với keyword
constructor
// Primary constructor
class User(val name: String, var age: Int)
// Tạo object
val user = User("Alice", 25)📝 Primary Constructor
Cú pháp cơ bản
// val/var tự động tạo property
class User(val name: String, var email: String)
// Equivalent verbose version
class User {
val name: String
var email: String
constructor(name: String, email: String) {
this.name = name
this.email = email
}
}Parameters vs Properties
class Example(
val property: String, // ✅ Tạo property (có getter)
var mutableProp: String, // ✅ Tạo property (getter + setter)
parameter: String // ❌ Chỉ là parameter, không tạo property
) {
// parameter chỉ available trong init block
init {
println("Parameter: $parameter")
}
// ❌ Không thể access parameter ở đây
// fun printParam() = println(parameter)
}💡
Quy tắc: Thêm val/var trước parameter để tự động tạo property. Không có val/var = chỉ là parameter cho constructor.
Default Values
class User(
val name: String,
val email: String = "",
val age: Int = 0,
val isActive: Boolean = true
)
fun main() {
val user1 = User("Alice") // Dùng default values
val user2 = User("Bob", "[email protected]") // Override một số
val user3 = User("Charlie", age = 30) // Named arguments
val user4 = User(
name = "David",
email = "[email protected]",
age = 25,
isActive = false
)
}Visibility Modifier cho Constructor
// Private constructor - cấm tạo object từ bên ngoài
class Singleton private constructor(val name: String) {
companion object {
val instance = Singleton("Default")
}
}
// Internal constructor
class InternalClass internal constructor(val data: String)
fun main() {
// val s = Singleton("Test") // ❌ Lỗi! Private constructor
val instance = Singleton.instance // ✅ OK
}🔄 init Blocks
Kotlin không cho phép code trực tiếp trong primary constructor. Dùng init block:
class User(val name: String, val email: String) {
// init block chạy ngay sau primary constructor
init {
println("Creating user: $name")
require(name.isNotBlank()) { "Name cannot be blank" }
require(email.contains("@")) { "Invalid email" }
}
// Property initialization
val normalizedName = name.trim().lowercase()
// Có thể có nhiều init blocks - chạy theo thứ tự khai báo
init {
println("Validation passed!")
}
}
fun main() {
val user = User("Alice", "[email protected]")
// Output:
// Creating user: Alice
// Validation passed!
}Thứ tự khởi tạo
class Demo(val param: String) {
// 1. Property initializers và init blocks chạy theo thứ tự khai báo
val first = println("1. First property").let { "first" }
init {
println("2. First init block")
}
val second = println("3. Second property").let { "second" }
init {
println("4. Second init block")
}
}
fun main() {
Demo("test")
// 1. First property
// 2. First init block
// 3. Second property
// 4. Second init block
}🔧 Secondary Constructor
Khi cần nhiều cách để tạo object:
class User(val name: String, val email: String, val age: Int) {
// Secondary constructor PHẢI delegate cho primary constructor
constructor(name: String) : this(name, "$name@example.com", 0) {
println("Created with name only")
}
constructor(name: String, email: String) : this(name, email, 0) {
println("Created with name and email")
}
// init block vẫn chạy trước secondary constructor body
init {
println("Init block runs first")
}
}
fun main() {
val user1 = User("Alice")
// Init block runs first
// Created with name only
println("Name: ${user1.name}, Email: ${user1.email}")
// Name: Alice, Email: [email protected]
}Delegation Chain
class Person(val name: String, val age: Int, val city: String) {
constructor(name: String, age: Int) : this(name, age, "Unknown")
constructor(name: String) : this(name, 0) // delegate to above constructor
constructor() : this("Guest") // delegate to above
}
fun main() {
val p1 = Person() // Guest, 0, Unknown
val p2 = Person("Alice") // Alice, 0, Unknown
val p3 = Person("Bob", 25) // Bob, 25, Unknown
val p4 = Person("Charlie", 30, "NYC") // Charlie, 30, NYC
}⚠️
Best Practice: Ưu tiên default parameters thay vì secondary constructors:
// ✅ Better: Default parameters
class User(
val name: String = "Guest",
val email: String = "",
val age: Int = 0
)
// ❌ Verbose: Secondary constructors
class User(val name: String, val email: String, val age: Int) {
constructor(name: String) : this(name, "", 0)
constructor(name: String, email: String) : this(name, email, 0)
}🎯 Validation trong Constructor
class BankAccount(
val accountNumber: String,
initialBalance: Double = 0.0
) {
var balance: Double = 0.0
private set
init {
require(accountNumber.length == 10) {
"Account number must be 10 digits"
}
require(initialBalance >= 0) {
"Initial balance cannot be negative"
}
balance = initialBalance
}
}
fun main() {
val account = BankAccount("1234567890", 1000.0)
println(account.balance) // 1000.0
// ❌ IllegalArgumentException: Account number must be 10 digits
// val invalid = BankAccount("123", 100.0)
}🛠️ Thực hành
Bài tập: Tạo class với multiple constructors
// TODO: Tạo class Product với:
// - name (required)
// - price (required)
// - quantity (default = 0)
// - category (default = "General")
// - totalValue computed property
// - Validation: price > 0, quantity >= 0Lời giải:
class Product(
val name: String,
val price: Double,
val quantity: Int = 0,
val category: String = "General"
) {
val totalValue: Double
get() = price * quantity
init {
require(name.isNotBlank()) { "Name cannot be blank" }
require(price > 0) { "Price must be positive" }
require(quantity >= 0) { "Quantity cannot be negative" }
}
override fun toString(): String {
return "$name ($category): $${"%.2f".format(price)} x $quantity = $${"%.2f".format(totalValue)}"
}
}
fun main() {
val laptop = Product("MacBook Pro", 2499.99, 5, "Electronics")
val book = Product("Kotlin Guide", 49.99)
println(laptop) // MacBook Pro (Electronics): $2499.99 x 5 = $12499.95
println(book) // Kotlin Guide (General): $49.99 x 0 = $0.00
}📱 Trong Android
// Custom View với multiple constructors
class CustomButton : AppCompatButton {
// Default constructor cho code
constructor(context: Context) : super(context) {
init(null)
}
// Constructor cho XML inflation
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(attrs)
}
// Constructor với style
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr) {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
// Initialize custom attributes
}
}
// ViewModel without default constructor
class UserViewModel(
private val repository: UserRepository,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ViewModel() {
// ViewModelFactory required
}
// Data class for network response
data class ApiUser(
val id: Long,
val name: String,
val email: String = "",
val avatarUrl: String? = null
)⚠️ Lưu ý quan trọng
💡
Thứ tự thực thi khi tạo object:
- Primary constructor arguments evaluated
- Superclass constructor (nếu có)
- Property initializers và init blocks (theo thứ tự khai báo)
- Secondary constructor body (nếu dùng)
⚠️
Tránh dùng this trong init block trước khi fully initialized:
class Problematic(val data: String) {
val length = data.length // ✅ OK
init {
registerSelf() // ⚠️ Nguy hiểm: object chưa fully initialized
}
fun registerSelf() { /* ... */ }
}✅ Checklist - Tự kiểm tra
Sau bài học này, bạn có thể:
- Tạo class với primary constructor
- Phân biệt
val/varparameter (property) vs không có modifier (parameter only) - Sử dụng default values cho constructor parameters
- Sử dụng init blocks cho initialization logic
- Hiểu thứ tự thực thi của init blocks
- Tạo secondary constructors với delegation
- Thực hiện validation trong constructor
Tiếp theo: Visibility Modifiers
Last updated on