Lesson 10 - Properties in Kotlin
In the previous lesson, Companion objects in Kotlin, we learned about companion objects. In today's Kotlin tutorial, we're going to look at other class features we don't know yet. Let's start with the promised getter and setter properties.
Getter and setter properties
We often want to have control over how an object property is being changed
from the outside of the class. We'd like to set the property as read-only or
react to its changes somehow. Let's create a new project (name it
Properties
) and add the following Student
class which
will represent a student in a database.
For the sake of this tutorial we'll only use var
in
the class, assuming that the data in the variables will change while changing
the person. At the same time, we'll try to get control over these changes.
class Student(var name: String, var male: Boolean, var age: Int) { var fullAged: Boolean init { fullAged = true if (age < 18) fullAged = false } override fun toString(): String { var iAmFullAged = "I'm" if (!fullAged) iAmFullAged = "I'm not" var gender = "male" if (!male) gender = "female" return "I'm $name, $gender. I'm $age years old and $iAmFullAged of age." } }
The class is very simple, the student has a name, gender, and age. The
fullAged
property is set according to the age and provides a more
comfortable determination whether the student is of age from different places in
the system. We use a Boolean
value to store the gender,
true
indicates that he's male. The constructor will determine
whether the student is of age or not. The toString()
method has
been altered to suit our needs. In a real world situation, it'd probably just
return the student's name. Let's create a student using the constructor:
val s = Student("Peter Brown", true, 20) println(s)
The output:
I'm Peter Brown, male. I'm 20 years old and I'm of age.
Everything looks nice, but the properties can be re-written. We can break the object like this, so it'll no longer function properly (i.e. would have an inconsistent internal state):
val s = Student("Peter Brown", true, 20) s.age = 15 s.male = false println(s)
The output:
I'm Peter Brown, female. I'm 15 years old and I'm of age.
Certainly, we would want fullAged to be updated when the age is
changed. Aside from that, there is no need to allow e.g. a gender to be changed.
However, want to keep the properties accessible for reading, so we can't make
them private
. In earlier lessons of our course, we've used methods
to read private properties or public val
variables. We'd name them
something like getAge()
and so on. We'll create get methods to be
able to read certain properties and make these properties private
to prevent them from being modified from the outside. The class would now look
something like this (I omitted the constructor and toString()
):
class Student(private var name: String, private var male: Boolean, private var age: Int) { var fullAged: Boolean fun getName(): String { return name } fun getFullAged(): Int { return fullAged } fun getAge(): Int { return age } fun isMale(): Boolean { return male } fun setAge(value: Int) { age = value // updating whether the student is of age fullAged = true if (age < 18) { fullAged = false } } }
The methods just returning a value are very simple. In the method setting the
age is some more logic, since we have to reconsider the fullAged
property. We made sure we can't set variables in any other way than what we
want. We now control all the property changes and can work with them as needed.
Our design must prevent all unwanted changes of the internal state that would
cause an object to malfunction.
Methods for returning values are called getters and methods
for setting values are called setters. We could potentially add
an editStudent()
method to edit the other properties sort of like
the constructor. Essentially, the student's name, age, and other properties
would be updated using this method. We could also validate the values being set
there since we'd be able to handle all attempts to change certain values in one
place.
However, asking for properties using methods takes time and could be confusing. That's why Kotlin provides other syntax as well.
private set
If we only want to prevent setting the properties from the outside, we can
use the private set
access modifier. We just place it in front of
the properties in our class, which will keep perfect encapsulation with no need
of getters (methods). We can delete them now.
Kotlin doesn't support using this modifier in the shortened version of the constructor, that's why we'll define the properties in the class's body.
class Student(name: String, male: Boolean, age: Int) { var name = name private set var male = male private set var age = age private set var fullAged: Boolean private set // the rest of the implementation... }
The properties are acting normally inside the class, however, we can't change them from the outside now.
Backing properties
We prevented any unwanted property changes from the outside elegantly. Using
a different technique, we can also achieve a property to behave like a method,
without writing the parenthesis ()
after its name when accessing
it. We can put this to use when having properties returning a value depending on
other properties. In Kotlin, such properties are called Backing properties. In
our class, we'll use it for fullAged
where, instead of the stored
Boolean
value, we'll return directly the age < 18
expression. This way, the property value will be always up to date. Let's modify
the fullAged
property:
var fullAged: Boolean = false get() { return age >= 18 }
Notice that the fullAged
property is still a
variable and that's why we can initialize it with the value of
false
or true
even though this value won't be ever
used.
After all the modifications, the class will look like this:
class Student(name: String, male: Boolean, age: Int) { var name = name private set var male = male private set var age = age private set var fullAged: Boolean = false private set get() { return age >= 18 } override fun toString(): String { var iAmFullAged = "I'm" if (!fullAged) iAmFullAged = "I'm not" var gender = "male" if (!male) gender = "female" return "I'm $name, $gender. I'm $age and $iAmFullAged of age." } }
We've finished a simple Kotlin getter that looks as a normal property, but it's real-only.
Of course, if you had a more complex calculation in your getter and accessed
it frequently, it'd be better to think about optimization and re-calculate the
value only when the property it depends on changes. In our case, it's the
age
property.
It'd look like this:
var age = age set(value) { fullAged = age >= 18 field = value // Sets `age` to `value` }
In this case, set(value)
is public; if we wanted to prevent
access from the outside, we'd use the private
access modifier.
If we tried to set age
to value
(the new value), the program would get into an infinite loop!
Now let's try to run the code that broke the internal state of the object before:
val s = Student("Peter Brown", true, 20) s.age = 15 //s.male = false // We have to comment this line out because it's not longer possible to change the gender from the outside println(s)
And the output is correct now:
I'm Peter Brown, male. I'm 15 years old and I'm not of age.
In Java, these Kotlin properties are shown as
getPropertyName()
or setPropertyName()
. It's because
Kotlin actually generates getters and setters in the background and Java then
uses them.
In the next lesson, Date and Time in Kotlin - Creating and formatting, we'll program a database using` Array`, a digital
diary!