Skip to main content

Kotlin Properties

Properties are essential building blocks in Kotlin classes that allow you to define the characteristics and state of your objects. Unlike some other programming languages that require explicit getters and setters, Kotlin properties provide a concise and elegant syntax while maintaining all the power and flexibility you might need.

What are Properties in Kotlin?

In Kotlin, properties are class members that represent the data held by a class. They combine the functionality of fields and accessor methods (getters and setters) in a single declaration.

kotlin
class Person {
var name: String = "John" // A mutable property
val birthYear: Int = 1990 // An immutable property
}

Property Declaration and Initialization

You can declare properties in Kotlin using either var (mutable) or val (read-only) keywords.

Basic Property Declaration

kotlin
class User {
var username: String = "default" // Mutable with initialization
val id: Int = 1 // Immutable with initialization
var email: String? = null // Nullable property
}

fun main() {
val user = User()
println(user.username) // Output: default

user.username = "john_doe" // Changing mutable property
println(user.username) // Output: john_doe

// user.id = 2 // This would cause a compilation error, as id is read-only
}

Property Declaration with Custom Accessors

You can customize the getter and setter behavior:

kotlin
class Rectangle {
var width: Int = 0
set(value) {
if (value >= 0) field = value
}

var height: Int = 0
set(value) {
if (value >= 0) field = value
}

val area: Int
get() = width * height // Custom getter
}

fun main() {
val rect = Rectangle()
rect.width = 5
rect.height = 10
println("Rectangle area: ${rect.area}") // Output: Rectangle area: 50

rect.width = -5 // This will be ignored due to our custom setter
println("Width: ${rect.width}") // Output: Width: 5
}

Backing Fields

When you define a custom accessor for a property, Kotlin provides a special identifier called field that represents the backing field for that property. The backing field is only available inside the accessors.

kotlin
class Temperature {
var celsius: Double = 0.0
set(value) {
field = value
fahrenheit = value * 9/5 + 32
}

var fahrenheit: Double = 32.0
set(value) {
field = value
celsius = (value - 32) * 5/9
}
}

fun main() {
val temp = Temperature()
temp.celsius = 25.0
println("Celsius: ${temp.celsius}, Fahrenheit: ${temp.fahrenheit}")
// Output: Celsius: 25.0, Fahrenheit: 77.0

temp.fahrenheit = 68.0
println("Celsius: ${temp.celsius}, Fahrenheit: ${temp.fahrenheit}")
// Output: Celsius: 20.0, Fahrenheit: 68.0
}

Late-Initialized Properties

Sometimes you need to declare a non-null property but can't initialize it at declaration time. For instance, when it's initialized in a setup method of a framework. For these cases, Kotlin provides the lateinit modifier:

kotlin
class DatabaseConnection {
lateinit var url: String

fun configure(serverAddress: String, port: Int) {
url = "jdbc:mysql://$serverAddress:$port/mydb"
println("Database connection configured")
}

fun connect() {
if (::url.isInitialized) {
println("Connecting to $url")
} else {
println("URL not configured yet!")
}
}
}

fun main() {
val db = DatabaseConnection()
db.connect() // Output: URL not configured yet!

db.configure("localhost", 3306)
db.connect() // Output: Connecting to jdbc:mysql://localhost:3306/mydb
}

Lazy Properties

A lazy property is only evaluated the first time it is accessed. This is useful for properties that are expensive to compute or might not be used in every execution of your program:

kotlin
class Resource {
val data: String by lazy {
println("Loading data...")
// Simulate loading data from external source
Thread.sleep(1000)
"Valuable data loaded"
}
}

fun main() {
val resource = Resource()
println("Resource instance created")

// data is not loaded yet
Thread.sleep(2000)
println("About to access data")

// Now data will be loaded
println(resource.data) // First access triggers lazy initialization
println(resource.data) // Second access just returns the cached value
}

/* Output:
Resource instance created
About to access data
Loading data...
Valuable data loaded
Valuable data loaded
*/

Delegated Properties

Kotlin allows you to delegate the implementation of property accessors to another object. This is a powerful feature that enables reuse of common property behaviors:

kotlin
import kotlin.properties.Delegates

class User {
var name: String by Delegates.observable("Initial") { property, oldValue, newValue ->
println("$oldValue -> $newValue")
}

var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
newValue >= 0 // Only allow non-negative ages
}
}

fun main() {
val user = User()
user.name = "John" // Output: Initial -> John
user.name = "Jane" // Output: John -> Jane

user.age = 25
println("Age: ${user.age}") // Output: Age: 25

user.age = -5 // Will be vetoed
println("Age: ${user.age}") // Output: Age: 25
}

Practical Example: Building a Configuration System

Let's see how properties can be used in a real-world scenario to build a simple configuration system:

kotlin
class ConfigurationManager {
// Default values
var serverUrl: String = "http://localhost:8080"
set(value) {
println("Server URL changed from $field to $value")
field = value
}

var maxConnections: Int = 10
set(value) {
if (value > 0) {
println("Max connections changed from $field to $value")
field = value
} else {
println("Invalid max connections value: $value (must be positive)")
}
}

val isSecure: Boolean
get() = serverUrl.startsWith("https")

val configuration: Map<String, Any>
get() = mapOf(
"serverUrl" to serverUrl,
"maxConnections" to maxConnections,
"isSecure" to isSecure
)
}

fun main() {
val config = ConfigurationManager()
println("Initial config: ${config.configuration}")

config.serverUrl = "https://api.example.com"
config.maxConnections = 20
println("Updated config: ${config.configuration}")

config.maxConnections = -5 // Will be rejected

println("Final config: ${config.configuration}")
}

/* Output:
Initial config: {serverUrl=http://localhost:8080, maxConnections=10, isSecure=false}
Server URL changed from http://localhost:8080 to https://api.example.com
Max connections changed from 10 to 20
Updated config: {serverUrl=https://api.example.com, maxConnections=20, isSecure=true}
Invalid max connections value: -5 (must be positive)
Final config: {serverUrl=https://api.example.com, maxConnections=20, isSecure=true}
*/

Summary

Kotlin properties provide a powerful and elegant way to handle class state:

  • Properties can be mutable (var) or read-only (val)
  • Custom accessors (getters and setters) can be defined to control access behavior
  • Backing fields are available within accessors using the field identifier
  • lateinit allows non-null properties to be initialized after construction
  • Lazy properties are evaluated only when accessed for the first time
  • Delegated properties enable reuse of property implementation patterns

By mastering properties in Kotlin, you'll be able to write cleaner, more maintainable code with better encapsulation and control over the state of your objects.

Exercises

  1. Create a BankAccount class with properties for balance and owner. Ensure the balance can't be set to a negative value.

  2. Implement a Temperature class that automatically converts between Celsius, Fahrenheit, and Kelvin units.

  3. Create a class that uses lazy initialization to load a large resource file only when it's first needed.

  4. Implement a delegated property that persists its value to a file whenever it changes.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)