Lesson 10 - Properties in Kotlin

Kotlin OOP 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! :)


 

 

Article has been written for you by Samuel Kodytek
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!