Skip to Content
Kotlin📘 Ngôn ngữ KotlinProperty Accessors (Getters/Setters)

Property Accessors trong Kotlin

1. Giới thiệu

Trong Kotlin, properties tự động có getter và setter. Bạn có thể tùy chỉnh chúng để thêm logic validation, computation, hoặc side effects.

2. Default Accessors

class Person { val name: String = "Alice" // Chỉ có getter (read-only) var age: Int = 0 // Có cả getter và setter } // Tương đương với: class PersonJavaStyle { private val _name: String = "Alice" private var _age: Int = 0 fun getName(): String = _name fun getAge(): Int = _age fun setAge(value: Int) { _age = value } }

3. Custom Getter

Computed Property

class Rectangle(val width: Int, val height: Int) { // Computed property - không lưu trữ giá trị val area: Int get() = width * height val perimeter: Int get() = 2 * (width + height) val isSquare: Boolean get() = width == height } fun main() { val rect = Rectangle(5, 10) println(rect.area) // 50 (tính mỗi lần gọi) println(rect.perimeter) // 30 println(rect.isSquare) // false }

Getter với logic phức tạp

class User(val firstName: String, val lastName: String) { val fullName: String get() = "$firstName $lastName" val initials: String get() = "${firstName.first()}${lastName.first()}" val displayName: String get() { return if (firstName.isNotBlank()) { fullName } else { "Anonymous" } } } fun main() { val user = User("John", "Doe") println(user.fullName) // John Doe println(user.initials) // JD println(user.displayName) // John Doe }

4. Custom Setter

Setter với validation

class Person { var name: String = "" set(value) { field = value.trim().replaceFirstChar { it.uppercase() } } var age: Int = 0 set(value) { if (value < 0) { throw IllegalArgumentException("Age cannot be negative") } field = value } } fun main() { val person = Person() person.name = " alice " println(person.name) // Alice person.age = 25 // OK // person.age = -5 // Throws IllegalArgumentException }

Setter với logging

class Configuration { var theme: String = "light" set(value) { println("Theme changing from '$field' to '$value'") field = value } var fontSize: Int = 14 set(value) { if (value != field) { println("Font size: $field -> $value") field = value } } } fun main() { val config = Configuration() config.theme = "dark" // Theme changing from 'light' to 'dark' config.fontSize = 16 // Font size: 14 -> 16 config.fontSize = 16 // (không in gì vì giá trị không đổi) }

5. Backing Field (field)

field là từ khóa đặc biệt chỉ dùng được trong accessors, đại diện cho giá trị thực của property:

class Counter { var count: Int = 0 get() = field set(value) { if (value >= 0) { field = value } } // Property không có backing field (computed) val isPositive: Boolean get() = count > 0 // Không dùng 'field', không có backing field }

Lưu ý: Nếu getter không tham chiếu field và setter không gán cho field, property sẽ không có backing field.

6. Backing Property Pattern

Khi cần kiểm soát nhiều hơn:

class User { // Private backing property private var _emails = mutableListOf<String>() // Public read-only view val emails: List<String> get() = _emails.toList() // Return immutable copy fun addEmail(email: String) { _emails.add(email) } } fun main() { val user = User() user.addEmail("alice@example.com") user.addEmail("alice@work.com") println(user.emails) // [alice@example.com, alice@work.com] // user.emails.add("x") // Error: List is read-only }

Trong ViewModel (Android)

class MyViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState.asStateFlow() private val _events = MutableSharedFlow<Event>() val events: SharedFlow<Event> = _events.asSharedFlow() }

7. lateinit Properties

Cho phép khởi tạo property sau constructor:

class UserProfile { lateinit var name: String lateinit var email: String fun isInitialized(): Boolean { return ::name.isInitialized && ::email.isInitialized } fun setup(name: String, email: String) { this.name = name this.email = email } } fun main() { val profile = UserProfile() println(profile.isInitialized()) // false profile.setup("Alice", "alice@example.com") println(profile.isInitialized()) // true println(profile.name) // Alice }

Quy tắc lateinit:

  • Chỉ dùng với var (không phải val)
  • Không dùng với primitive types (Int, Boolean, etc.)
  • Không có custom getter/setter
  • Phải khởi tạo trước khi sử dụng
class Example { // lateinit var count: Int // ERROR: primitive type lateinit var name: String // OK // Kiểm tra đã khởi tạo chưa fun printName() { if (::name.isInitialized) { println(name) } else { println("Not initialized") } } }

8. Accessor Visibility Modifiers

Có thể set visibility riêng cho setter:

class Counter { var count: Int = 0 private set // Getter public, setter private fun increment() { count++ } } class User { var id: String = "" internal set // Setter chỉ accessible trong module var nickname: String = "" protected set // Setter chỉ accessible trong subclasses } fun main() { val counter = Counter() println(counter.count) // OK: getter is public // counter.count = 10 // ERROR: setter is private counter.increment() // OK }

9. Properties trong Interface

interface Named { val name: String // Abstract property val greetingMessage: String get() = "Hello, $name!" // Property với default getter } class Person(override val name: String) : Named class Robot : Named { override val name: String get() = "Robot-${hashCode()}" } fun main() { val person = Person("Alice") println(person.name) // Alice println(person.greetingMessage) // Hello, Alice! val robot = Robot() println(robot.greetingMessage) // Hello, Robot-12345678! }

10. Best Practices

✅ Do

class User { // Computed property cho derived values val displayName: String get() = "$firstName $lastName" // Setter với validation var email: String = "" set(value) { require(value.contains("@")) { "Invalid email" } field = value } // Private set cho controlled mutation var loginCount: Int = 0 private set }

❌ Don’t

class BadExample { // Avoid: Heavy computation trong getter val processedData: List<Data> get() { return fetchFromNetwork() // Called every access! } // Avoid: Side effects trong getter var counter: Int = 0 get() { field++ // Modifying trong getter! return field } }

Khi nào dùng computed vs stored property

class Rectangle(val width: Int, val height: Int) { // ✅ Computed: cheap, derived từ other properties val area: Int get() = width * height // ✅ Stored: expensive computation, cache kết quả val expensiveValue: String by lazy { // One-time expensive computation computeExpensiveValue() } }

📝 Tóm tắt

FeatureDescription
Custom getterget() = expression
Custom setterset(value) { field = value }
fieldBacking field trong accessors
lateinitKhởi tạo sau, chỉ cho var non-primitive
private setSetter private, getter public
Backing property_propName pattern

Best practices:

  • Dùng computed properties cho derived values
  • Dùng setter validation để enforce invariants
  • Dùng private set cho controlled mutation
  • Avoid side effects trong getters
  • Dùng lateinit khi initialization timing không xác định
Last updated on