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

🏁 Constructor

🎯 Mục tiêu: Hiểu Primary Constructor, Secondary Constructor, và init blocks - cách khởi tạo object trong Kotlin.


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)

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

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!
}
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
}

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]
}
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
}

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

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 >= 0

Lờ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
}

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


Sau bài học này, bạn có thể:

  • Tạo class với primary constructor
  • Phân biệt val/var parameter (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