# Property Accessors
To read a property, use its name. To assign a value to a mutable property, use the assignment operator
=
.
This reads and writes the property i
:
// PropertyAccessors/Data.kt
package propertyaccessors
import atomictest.eq
class Data(var i: Int)
fun main() {
val data = Data(10)
data.i eq 10 // Read the 'i' property
data.i = 20 // Write to the 'i' property
}
This appears to be straightforward access to the piece of storage named i
. However, Kotlin calls functions to perform the read and write operations. As you expect, the default behavior of those functions reads and writes the data stored in i
. In this atom you’ll learn to write your own property accessors to customize the reading and writing actions.
The accessor used to get the value of a property is called a getter. You create a getter by defining get()
immediately after the property definition. The accessor used to modify a mutable property is called a setter. You create a setter by defining set()
immediately after the property definition.
The property accessors defined in the following example imitate the default implementations generated by Kotlin. We display additional information so you can see that the property accessors are indeed called during reads and writes. We indent get()
and set()
to visually associate them with the property, but the actual association happens because get()
and set()
are defined immediately after that property (Kotlin doesn’t care about the indentation):
// PropertyAccessors/Default.kt
package propertyaccessors
import atomictest.*
class Default {
var i: Int = 0
get() {
trace("get()")
return field // [1]
}
set(value) {
trace("set($value)")
field = value // [2]
}
}
fun main() {
val d = Default()
d.i = 2
trace(d.i)
trace eq """
set(2)
get()
2
"""
}
The definition order for get()
and set()
is unimportant. You can define get()
without defining set()
, and vice-versa.
The default behavior for a property returns its stored value from a getter and modifies it with a setter—the actions of [1] and [2]. Inside the getter and setter, the stored value is manipulated indirectly using the field
keyword, which is only accessible within these two functions.
This next example uses the default implementation of the getter and adds a setter to trace changes to the property n
:
// PropertyAccessors/LogChanges.kt
package propertyaccessors
import atomictest.*
class LogChanges {
var n: Int = 0
set(value) {
trace("$field becomes $value")
field = value
}
}
fun main() {
val lc = LogChanges()
lc.n eq 0
lc.n = 2
lc.n eq 2
trace eq "0 becomes 2"
}
If you define a property as private
, both accessors become private
. You can also make the setter private
and the getter public
. Then you can read the property outside the class, but only change its value inside the class:
// PropertyAccessors/Counter.kt
package propertyaccessors
import atomictest.eq
class Counter {
var value: Int = 0
private set
fun inc() = value++
}
fun main() {
val counter = Counter()
repeat(10) {
counter.inc()
}
counter.value eq 10
}
Using private set
, we control the value
property so it can only be incremented by one.
Normal properties store their data in a field. You can also create a property that doesn’t have a field:
// PropertyAccessors/Hamsters.kt
package propertyaccessors
import atomictest.eq
class Hamster(val name: String)
class Cage(private val maxCapacity: Int) {
private val hamsters =
mutableListOf<Hamster>()
val capacity: Int
get() = maxCapacity - hamsters.size
val full: Boolean
get() = hamsters.size == maxCapacity
fun put(hamster: Hamster): Boolean =
if (full)
false
else {
hamsters += hamster
true
}
fun take(): Hamster =
hamsters.removeAt(0)
}
fun main() {
val cage = Cage(2)
cage.full eq false
cage.capacity eq 2
cage.put(Hamster("Alice")) eq true
cage.put(Hamster("Bob")) eq true
cage.full eq true
cage.capacity eq 0
cage.put(Hamster("Charlie")) eq false
cage.take()
cage.capacity eq 1
}
The properties capacity
and full
contain no underlying state—they are computed at the time of each access. Both capacity
and full
are similar to functions, and you can define them as such:
// PropertyAccessors/Hamsters2.kt
package propertyaccessors
class Cage2(private val maxCapacity: Int) {
private val hamsters =
mutableListOf<Hamster>()
fun capacity(): Int =
maxCapacity - hamsters.size
fun isFull(): Boolean =
hamsters.size == maxCapacity
}
In this case, using properties improves readability because capacity and fullness are properties of the cage. However, don’t just convert all your functions to properties—first, see how they read.
- -
The Kotlin style guide prefers properties over functions when the value is cheap to calculate and the property returns the same result for each invocation as long as the object state hasn’t changed.
Property accessors provide a kind of protection for properties. Many object-oriented languages rely on making a physical field private
to control access to that property. With property accessors you can add code to control or modify that access, while allowing anyone to use a property.
Exercises and solutions can be found at www.AtomicKotlin.com.