You can declare properties inside Kotlin classes in the same way you declare any other variable. These properties can be mutable (declared using var
) or immutable (declared using val
).
Here is an example -
// User class with a Primary constructor that accepts three parameters
class User(_id: Int, _name: String, _age: Int) {
// Properties of User class
val id: Int = _id // Immutable (Read only)
var name: String = _name // Mutable
var age: Int = _age // Mutable
}
You can get or set the properties of an object using the dot(.
) notation like so -
val user = User(1, "Jack Sparrow", 44)
// Getting a Property
val name = user.name
// Setting a Property
user.age = 46
// You cannot set read-only properties
user.id = 2 // Error: Val cannot be assigned
Getters and Setters
Kotlin internally generates a default getter and setter for mutable properties, and a getter (only) for read-only properties.
It calls these getters and setters internally whenever you access or modify a property using the dot(.
) notation.
Here is how the User
class that we saw in the previous section looks like with the default getters and setters -
class User(_id: Int, _name: String, _age: Int) {
val id: Int = _id
get() = field
var name: String = _name
get() = field
set(value) {
field = value
}
var age: Int = _age
get() = field
set(value) {
field = value
}
}
You might have noticed two strange identifiers in all the getter and setter methods - field
and value
.
Let’s understand what these identifiers are for -
1. value
We use value
as the name of the setter parameter. This is the default convention in Kotlin but you’re free to use any other name if you want.
The value
parameter contains the value that a property is assigned to. For example, when you write user.name = "Bill Gates"
, the value
parameter contains the assigned value “Bill Gates”.
2. Backing Field (field
)
Backing field helps you refer to the property inside the getter and setter methods. This is required because if you use the property directly inside the getter or setter then you’ll run into a recursive call which will generate a StackOverflowError.
Let’s see that in action. Let’s modify the User
class and refer to the properties directly inside the getters and setters instead of using the field
identifier -
class User(_name: String, _age: Int) {
var name: String = _name
get() = name // Calls the getter recursively
var age: Int = _age
set(value) {
age = value // Calls the setter recursively
}
}
fun main(args: Array<String>) {
val user = User("Jack Sparrow", 44)
// Getting a Property
println("${user.name}") // StackOverflowError
// Setting a Property
user.age = 45 // StackOverflowError
}
The above program will generate a StackOverflowError due to the recursive calls in the getter and setter methods. This is why Kotlin has the concept of backing fields. It makes storing the property value in memory possible. When you initialize a property with a value in the class, the initializer value is written to the backing field of that property -
class User(_id: Int) {
val id: Int = _id // The initializer value is written to the backing field of the property
}
A backing field is generated for a property if
- A custom getter/setter references it through the
field
identifier or, - It uses the default implementation of at least one of the accessors (getter or setter). (Remember, the default getter and setter references the
field
identifier themselves)
For example, a backing field is not generated for the id
property in the following class because it neither uses the default implementation of the getter nor refers to the field
identifier in the custom getter -
class User() {
val id // No backing field is generated
get() = 0
}
Since a backing field is not generated, you won’t be able to initialize the above property like so -
class User(_id: Int) {
val id: Int = _id //Error: Initialization not possible because no backing field is generated which could store the initialized value in memory
get() = 0
}
Creating Custom Getters and Setters
You can ditch Kotlin’s default getter/setter and define a custom getter and setter for the properties of your class.
Here is an example -
class User(_id: Int, _name: String, _age: Int) {
val id: Int = _id
var name: String = _name
// Custom Getter
get() {
return field.toUpperCase()
}
var age: Int = _age
// Custom Setter
set(value) {
field = if(value > 0) value else throw IllegalArgumentException("Age must be greater than zero")
}
}
fun main(args: Array<String>) {
val user = User(1, "Jack Sparrow", 44)
println("${user.name}") // JACK SPARROW
user.age = -1 // Throws IllegalArgumentException: Age must be greater that zero
}
Conclusion
Thanks for reading folks. In this article, You learned how Kotlin’s getters and setters work and how to define a custom getter and setter for the properties of a class.
I tried my best to explain all the concepts. But if you still have any doubt, please feel free to clarify that in the comment section below.