Skip to Content
Kotlin📘 Ngôn ngữ Kotlin🏗️ Class và Object

Class và Object trong Kotlin

🎯 Mục tiêu: Hiểu khái niệm OOP cơ bản, cách định nghĩa class, tạo object, và các tính năng đặc biệt của Kotlin như primary constructor.


💡 Lập trình hướng đối tượng (OOP) là gì?

Hãy tưởng tượng bạn đang xây một ngôi nhà:

  • Class = Bản thiết kế nhà (blueprint)
  • Object = Ngôi nhà thực tế được xây từ bản thiết kế

Một bản thiết kế có thể xây nhiều ngôi nhà giống nhau, tương tự một class có thể tạo nhiều objects.

// Class = Bản thiết kế class Person(val name: String, var age: Int) fun main() { // Objects = Các thực thể được tạo từ class val alice = Person("Alice", 25) val bob = Person("Bob", 30) println(alice.name) // Alice println(bob.name) // Bob }

4 nguyên lý OOP cơ bản

Nguyên lýMô tảVí dụ
EncapsulationĐóng gói dữ liệu và phương thứcprivate var password
InheritanceKế thừa từ class chaclass Dog : Animal()
PolymorphismĐa hình - nhiều hình thứcOverride methods
AbstractionTrừu tượng hóaInterface, abstract class

📝 Định nghĩa Class

Class đơn giản

// Class rỗng class Empty // Class với properties class User { var name: String = "" var email: String = "" } fun main() { val user = User() user.name = "Alice" user.email = "[email protected]" }

Primary Constructor - Cách Kotlin

Kotlin có syntax ngắn gọn và mạnh mẽ:

// Primary constructor với val/var tự động tạo properties class User(val name: String, var email: String) // Tương đương với Java (20+ dòng code!): // class User { // private final String name; // private String email; // public User(String name, String email) { // this.name = name; // this.email = email; // } // public String getName() { return name; } // public String getEmail() { return email; } // public void setEmail(String email) { this.email = email; } // }
💡

val = readonly property (chỉ getter)
var = mutable property (getter + setter)
Không có val/var = chỉ là parameter, không tạo property

init block - Khởi tạo logic

class User(val name: String, val email: String) { // init block chạy ngay sau khi object được tạo init { println("Creating user: $name") require(name.isNotBlank()) { "Name cannot be blank" } require(email.contains("@")) { "Invalid email format" } } // Có thể có nhiều init blocks init { println("User validated successfully!") } } fun main() { val user = User("Alice", "[email protected]") // Output: // Creating user: Alice // User validated successfully! }

🔧 Secondary Constructor

Khi cần nhiều cách để tạo object:

class User(val name: String, val email: String) { // Secondary constructor phải gọi primary constructor constructor(name: String) : this(name, "$name@example.com") { println("Created with default email") } constructor() : this("Guest") { println("Created as guest") } } fun main() { val u1 = User("Alice", "[email protected]") // Primary val u2 = User("Bob") // Secondary 1 val u3 = User() // Secondary 2 println(u2.email) // [email protected] println(u3.name) // Guest }
ℹ️

Best Practice: Ưu tiên sử dụng default parameters thay vì secondary constructors:

class User( val name: String = "Guest", val email: String = "$name@example.com" )

📦 Properties

Computed Properties (Getter tùy chỉnh)

class Rectangle(val width: Int, val height: Int) { // Computed property - tính toán mỗi lần truy cập val area: Int get() = width * height val perimeter: Int get() = 2 * (width + height) val isSquare: Boolean get() = width == height } fun main() { val rect = Rectangle(10, 5) println("Area: ${rect.area}") // 50 println("Perimeter: ${rect.perimeter}") // 30 println("Is square: ${rect.isSquare}") // false }

Custom Setter

class User(val id: Int) { var name: String = "" set(value) { // 'field' là backing field field = value.trim().replaceFirstChar { it.uppercase() } } var email: String = "" set(value) { require(value.contains("@")) { "Invalid email" } field = value.lowercase() } } fun main() { val user = User(1) user.name = " alice " user.email = "[email protected]" println(user.name) // Alice println(user.email) // [email protected] }

Private Setter

class Counter { var count: Int = 0 private set // Chỉ class này có thể thay đổi fun increment() { count++ } } fun main() { val counter = Counter() counter.increment() println(counter.count) // 1 // counter.count = 100 // ❌ Lỗi! Không thể set từ bên ngoài }

Late-initialized Properties

class UserProfile { // Sẽ được khởi tạo sau (ví dụ: sau khi load từ API) lateinit var username: String fun loadFromApi() { // Giả sử load từ API username = "loaded_user" } fun displayUsername() { if (::username.isInitialized) { println("Username: $username") } else { println("Username not loaded yet") } } }
⚠️

lateinit chỉ dùng với:

  • var (không dùng với val)
  • Non-primitive types (không dùng với Int, Double, Boolean)
  • Nếu truy cập trước khi khởi tạo → UninitializedPropertyAccessException

🔒 Visibility Modifiers

ModifierTrong classTop-level
public (mặc định)Visible everywhereVisible everywhere
privateChỉ trong classChỉ trong file
protectedClass + subclassesN/A (không dùng)
internalModule hiện tạiModule hiện tại
class BankAccount(private val accountNumber: String) { private var encryptedPassword: String = "" // Chỉ trong class var balance: Double = 0.0 private set // Public get, private set internal fun auditLog() { // Trong module println("Audit: $accountNumber") } protected open fun calculateInterest(): Double { // Subclasses return balance * 0.05 } }

🔑 Methods (Hàm trong Class)

class Calculator { private var history = mutableListOf<String>() fun add(a: Int, b: Int): Int { val result = a + b history.add("$a + $b = $result") return result } fun subtract(a: Int, b: Int): Int { val result = a - b history.add("$a - $b = $result") return result } fun printHistory() { println("=== Lịch sử tính toán ===") history.forEach { println(it) } } } fun main() { val calc = Calculator() calc.add(10, 5) // 15 calc.subtract(10, 3) // 7 calc.printHistory() // === Lịch sử tính toán === // 10 + 5 = 15 // 10 - 3 = 7 }

🛠️ Thực hành

Bài tập 1: Tạo class Student

// TODO: Tạo class Student với: // - id (readonly) // - name (readonly) // - scores (mutable list) // - averageScore (computed property) // - addScore(score: Double) // - getGrade(): String (A/B/C/D/F)

Lời giải:

class Student(val id: String, val name: String) { private val scores = mutableListOf<Double>() val averageScore: Double get() = if (scores.isEmpty()) 0.0 else scores.average() fun addScore(score: Double) { require(score in 0.0..10.0) { "Score must be 0-10" } scores.add(score) } fun getGrade(): String = when { averageScore >= 9.0 -> "A" averageScore >= 8.0 -> "B" averageScore >= 7.0 -> "C" averageScore >= 5.0 -> "D" else -> "F" } override fun toString(): String { return "Student($name, avg=${"%.2f".format(averageScore)}, grade=${getGrade()})" } } fun main() { val student = Student("SV001", "Nguyễn Văn A") student.addScore(8.5) student.addScore(9.0) student.addScore(7.5) println(student) // Student(Nguyễn Văn A, avg=8.33, grade=B) }

Bài tập 2: Tạo class BankAccount với validation

// TODO: Tạo class BankAccount với: // - accountNumber (readonly) // - ownerName (readonly) // - balance (private set, khởi tạo = 0) // - deposit(amount): validate amount > 0 // - withdraw(amount): Boolean - trả về true nếu thành công // - transfer(to: BankAccount, amount): Boolean

Lời giải:

class BankAccount( val accountNumber: String, val ownerName: String ) { var balance: Double = 0.0 private set fun deposit(amount: Double) { require(amount > 0) { "Amount must be positive" } balance += amount println("Deposited $${"%.2f".format(amount)}. New balance: $${"%.2f".format(balance)}") } fun withdraw(amount: Double): Boolean { require(amount > 0) { "Amount must be positive" } return if (amount <= balance) { balance -= amount println("Withdrew $${"%.2f".format(amount)}. New balance: $${"%.2f".format(balance)}") true } else { println("Insufficient funds! Balance: $${"%.2f".format(balance)}") false } } fun transfer(to: BankAccount, amount: Double): Boolean { if (withdraw(amount)) { to.deposit(amount) println("Transferred $${"%.2f".format(amount)} to ${to.ownerName}") return true } return false } } fun main() { val alice = BankAccount("001", "Alice") val bob = BankAccount("002", "Bob") alice.deposit(1000.0) alice.transfer(bob, 300.0) println("\nFinal balances:") println("Alice: $${alice.balance}") // 700.0 println("Bob: $${bob.balance}") // 300.0 }

📱 Trong Android

// Model class cho User class User( val id: Long, val name: String, val email: String, var isActive: Boolean = true ) { val displayName: String get() = if (name.isNotBlank()) name else "User #$id" } // ViewModel với state class UserViewModel : ViewModel() { private val _users = MutableLiveData<List<User>>() val users: LiveData<List<User>> = _users fun loadUsers() { viewModelScope.launch { val result = repository.getUsers() _users.value = result } } } // ViewBinding với custom class class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var viewModel: UserViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) viewModel = ViewModelProvider(this)[UserViewModel::class.java] observeUsers() } private fun observeUsers() { viewModel.users.observe(this) { users -> binding.userCount.text = "Users: ${users.size}" } } }

⚠️ Lưu ý quan trọng

⚠️

Không dùng var trong constructor khi không cần thiết:

// ❌ Cho phép thay đổi từ bên ngoài - có thể gây bug class User(var id: Int, var name: String) // ✅ Id không nên thay đổi class User(val id: Int, var name: String)
💡

Ưu tiên Data Class cho model classes:

// Thay vì viết thủ công class User(val id: Int, val name: String) { override fun equals(other: Any?): Boolean { ... } override fun hashCode(): Int { ... } override fun toString(): String { ... } } // ✅ Dùng data class data class User(val id: Int, val name: String) // Tự động có equals, hashCode, toString, copy!

✅ Checklist - Tự kiểm tra

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

  • Hiểu 4 nguyên lý OOP: Encapsulation, Inheritance, Polymorphism, Abstraction
  • Phân biệt Class (blueprint) và Object (instance)
  • Tạo class với primary constructor
  • Sử dụng val (readonly) và var (mutable) cho properties
  • Sử dụng init block để khởi tạo logic
  • Tạo computed properties (custom getter)
  • Sử dụng custom setter và private setter
  • Hiểu visibility modifiers: public, private, protected, internal
  • Biết khi nào dùng lateinit

Tiếp theo: Property Accessors

Last updated on