In Kotlin, class properties can be declared as mutable using var or read-only using val.
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
fun duplicateAddress(source: Address): Address {
val copy = Address()
copy.name = source.name
copy.street = source.street
// ...
return copy
}
Getters and Setters
The full syntax for declaring a property is:
var <name>[: <Type>] [= <initializer>]
[<getter>]
[<setter>]
The initializer, getter, and setter are optional. The type can often be inferred from the initializer or the getter’s return type.
// var allByDefault: Int? // ❌ Compilation error: must be initialized
var inferred = 1 // ✅ Type inferred as Int with default accessors
Read-only properties (val) cannot have setters:
val needsInit: Int // Must be initialized in constructor
val inferredVal = 1 // Type inferred as Int
Custom accessors allow computed or side-effecting behavior:
val isEmpty: Boolean get() = size == 0
var stringRepresentation: String
get() = toString()
set(value) {
parseAndAssign(value)
}
Since Kotlin 1.1, the property type can be omitted if it can be inferred from the getter:
val isEmpty get() = size == 0 // Type is Boolean
Visibility or annotations can be applied to accessors without changing their implementation:
var internalSetter: String = "default"
private set
var injectedValue: Any? = null
@Inject set
Backing Fields
Kotlin automatical provides a backing field when needed, accessible via the field identifier inside accessors:
var counter = 0
set(value) {
if (value >= 0) field = value
}
The field keyword is only valid within property accessors.
Backing Properties
When automatic backing fields aren’t sufficient, a backing property can be used:
private var _table: Map<String, Int>? = null
val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap()
}
return _table ?: error("Concurrent modification")
}
On the JVM, accessing such private backing properties incurs no extra functon call overhead due to compiler optimizations.
Compile-Time Constants
Read-only properties known at compile time can be marked with const if they meet specific criteria:
- Declared at top level, in an
object, or in acompanion object - Initialized with a
Stringor primitive type - Have no custom getter
const val DEPRECATION_MESSAGE = "This feature is deprecated"
@Deprecated(DEPRECATION_MESSAGE)
fun outdatedFunction() { /* ... */ }
Late Initialization
Non-null properties usually require initialization in the constructor. For cases like dependency injection or test setup, use lateinit:
class IntegrationTest {
lateinit var service: ExternalService
@BeforeEach
fun setUp() {
service = ExternalService()
}
@Test
fun verifyBehavior() {
service.execute()
}
}
Starting from Kotlin 1.2, you can check if a lateinit property has been initialized:
if (this::service.isInitialized) {
println(service.status)
}