In Kotlin programming, properties are fundamental building blocks that simplify data encapsulation compared to traditional Java getters and setters. When you declare a Kotlin property, the compiler automatically generates default getter and setter methods behind the scenes.
Kotlin properties can be declared using two keywords:
var
- Creates a mutable property with both getter and setterval
- Creates an immutable property with only a getterclass User {
var name: String = "Default Name" // Mutable property with getter and setter
val userId: Int = 12345 // Immutable property with only getter
}
Here’s how Kotlin automatic getter setter generation works:
class Person {
var age: Int = 25
get() = field // Default getter returns the backing field
set(value) { // Default setter assigns value to backing field
field = value
}
}
Kotlin property access is seamless and doesn’t require explicit method calls:
fun main() {
val person = Person()
// Using implicit setter
person.age = 30
// Using implicit getter
println("Person's age: ${person.age}")
}
Custom getters in Kotlin allow you to add logic when retrieving property values. This is particularly useful for computed properties and derived values.
A custom getter is defined using the get()
function after the property declaration:
class Rectangle {
var width: Double = 0.0
var height: Double = 0.0
// Custom getter for computed property
val area: Double
get() = width * height
// Custom getter with validation logic
val isSquare: Boolean
get() = width == height
}
Kotlin getter transformation allows you to modify returned values:
class UserProfile {
var email: String = ""
// Custom getter that always returns lowercase email
val normalizedEmail: String
get() = email.lowercase().trim()
// Custom getter with conditional logic
val displayName: String
get() = if (email.isNotEmpty()) email.substringBefore("@") else "Anonymous"
}
Custom setters in Kotlin enable you to add validation, transformation, or side effects when assigning values to properties.
A custom setter is defined using the set(value)
function:
class BankAccount {
var balance: Double = 0.0
set(value) {
if (value >= 0) {
field = value
} else {
println("Balance cannot be negative!")
}
}
}
Kotlin property validation through custom setters ensures data integrity:
class UserAccount {
var password: String = ""
set(value) {
require(value.length >= 8) { "Password must be at least 8 characters" }
require(value.any { it.isDigit() }) { "Password must contain a digit" }
field = value
}
var age: Int = 0
set(value) {
require(value in 0..150) { "Age must be between 0 and 150" }
field = value
}
}
Kotlin setter side effects can trigger additional operations:
class TemperatureMonitor {
var temperature: Double = 20.0
set(value) {
field = value
notifyObservers(value)
logTemperatureChange(value)
}
private fun notifyObservers(temp: Double) {
println("Temperature changed to: $temp°C")
}
private fun logTemperatureChange(temp: Double) {
// Log to file or database
println("Logging temperature: $temp")
}
}
Kotlin backing fields are essential for property storage and preventing infinite recursion in custom accessors.
The field
identifier in Kotlin backing field refers to the actual storage location of the property:
class Counter {
var count: Int = 0
get() = field // Returns the backing field value
set(value) {
if (value >= 0) {
field = value // Assigns to the backing field
}
}
}
Kotlin backing field generation follows specific rules:
field
identifierclass Example {
// Backing field generated (uses default accessors)
var property1: String = "value"
// Backing field generated (custom accessor uses 'field')
var property2: String = "value"
set(value) {
field = value.uppercase()
}
// No backing field generated (no default accessors, no 'field' reference)
val property3: String
get() = "computed value"
}
Kotlin recursive setter prevention is crucial for avoiding stack overflow errors:
class SafeProperty {
var value: String = ""
get() = field // Correct: uses backing field
set(newValue) {
field = newValue // Correct: assigns to backing field
}
}
class UnsafeProperty {
var value: String = ""
get() = value // Wrong: causes infinite recursion
set(newValue) {
value = newValue // Wrong: causes infinite recursion
}
}
Kotlin private setter allows you to create read-only properties from outside the class while maintaining write access internally.
class GameScore {
var score: Int = 0
private set // Public getter, private setter
var playerName: String = ""
private set(value) { // Custom private setter
field = value.trim().takeIf { it.isNotEmpty() } ?: "Anonymous"
}
fun increaseScore(points: Int) {
score += points
}
fun setPlayerName(name: String) {
playerName = name
}
}
Kotlin encapsulation through private setters provides controlled access:
class DatabaseConnection {
var isConnected: Boolean = false
private set
var connectionUrl: String = ""
private set
fun connect(url: String): Boolean {
connectionUrl = url
isConnected = establishConnection(url)
return isConnected
}
fun disconnect() {
isConnected = false
connectionUrl = ""
}
private fun establishConnection(url: String): Boolean {
// Connection logic here
return true
}
}
Kotlin lazy properties combined with custom getters provide efficient initialization:
class DataProcessor {
private var _data: String? = null
val processedData: String
get() {
if (_data == null) {
_data = loadAndProcessData()
}
return _data!!
}
private fun loadAndProcessData(): String {
// Expensive data processing
return "Processed data"
}
}
Kotlin property delegation offers advanced property management:
import kotlin.properties.Delegates
class ObservableProperties {
var observedProperty: String by Delegates.observable("initial") {
property, oldValue, newValue ->
println("Property ${property.name} changed from $oldValue to $newValue")
}
var vetoableProperty: Int by Delegates.vetoable(0) {
property, oldValue, newValue ->
newValue >= 0 // Only allow non-negative values
}
}
Kotlin backing properties provide complete control over property access:
class SecureData {
private var _sensitiveData: String? = null
val sensitiveData: String?
get() = _sensitiveData?.let { decrypt(it) }
fun setSensitiveData(data: String) {
_sensitiveData = encrypt(data)
}
private fun encrypt(data: String): String = "encrypted_$data"
private fun decrypt(data: String): String = data.removePrefix("encrypted_")
}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
class UserProfileViewModel : ViewModel() {
private val _username = MutableLiveData<String>()
val username: LiveData<String> = _username
var userInput: String = ""
set(value) {
field = value.trim()
_username.value = field
validateInput(field)
}
var isValid: Boolean = false
private set
private fun validateInput(input: String) {
isValid = input.length >= 3 && input.isNotBlank()
}
}
data class MobileUser(
private var _phoneNumber: String,
private var _email: String
) {
var phoneNumber: String
get() = _phoneNumber
set(value) {
require(isValidPhoneNumber(value)) { "Invalid phone number format" }
_phoneNumber = value
}
var email: String
get() = _email.lowercase()
set(value) {
require(isValidEmail(value)) { "Invalid email format" }
_email = value
}
private fun isValidPhoneNumber(phone: String): Boolean {
return phone.matches(Regex("^\\+?[1-9]\\d{1,14}\$"))
}
private fun isValidEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
init {
phoneNumber = _phoneNumber
email = _email
}
}
Here’s a comprehensive example demonstrating Kotlin getter setter concepts in a real-world scenario:
import kotlin.math.max
import kotlin.math.min
/**
* Mobile App User Profile with comprehensive property management
* Demonstrates Kotlin getters, setters, backing fields, and validation
*/
class MobileAppUser(initialEmail: String) {
// Property with custom setter validation
var email: String = ""
set(value) {
require(value.isNotEmpty()) { "Email cannot be empty" }
require(value.contains("@")) { "Email must contain @ symbol" }
field = value.lowercase().trim()
println("Email updated to: $field")
}
get() = field
// Property with private setter and validation
var profileScore: Int = 0
private set(value) {
field = max(0, min(100, value)) // Clamp between 0 and 100
}
// Computed property with custom getter
val emailDomain: String
get() = email.substringAfter("@")
// Property with custom getter and backing field
var _displayName: String = ""
var displayName: String
get() = if (_displayName.isNotEmpty()) _displayName else email.substringBefore("@")
set(value) {
_displayName = value.trim().take(50) // Limit to 50 characters
}
// Property with side effects in setter
var isActive: Boolean = true
set(value) {
if (field != value) { // Only act on actual changes
field = value
onActiveStatusChanged(value)
}
}
// Read-only computed property
val userSummary: String
get() = "User: $displayName ($email) - Score: $profileScore - Active: $isActive"
init {
email = initialEmail
updateProfileScore(25) // Initial score
}
// Public method to update private setter property
fun updateProfileScore(newScore: Int) {
profileScore = newScore
}
fun increaseScore(points: Int) {
profileScore += points
}
fun deactivateUser() {
isActive = false
}
private fun onActiveStatusChanged(active: Boolean) {
println("User ${displayName} is now ${if (active) "active" else "inactive"}")
// Could trigger analytics, notifications, etc.
}
}
// Usage example
fun main() {
// Create user with validation
val user = MobileAppUser("john.doe@example.com")
// Property assignments trigger custom setters
user.displayName = "John Doe"
user.increaseScore(25)
// Access computed properties
println("Email domain: ${user.emailDomain}")
println("User summary: ${user.userSummary}")
// Trigger side effects
user.deactivateUser()
// Attempt invalid email (will throw exception)
try {
user.email = "invalid-email"
} catch (e: IllegalArgumentException) {
println("Validation error: ${e.message}")
}
}
Output:
Email updated to: john.doe@example.com
Email domain: example.com
User summary: User: John Doe (john.doe@example.com) - Score: 50 - Active: true
User John Doe is now inactive
Validation error: Email must contain @ symbol
This example showcases all major Kotlin property concepts including custom getters, custom setters, backing fields, property validation, private setters, computed properties, and side effects in a practical Android development context.