🚗 · Accessors, continued ⚙️

3 min read · Updated on by

Let’s emphasize the difference between defining a custom getter vs. initializing a property directly:


//sampleStart
import java.time.Instant

class TimeOfStuff {
    // This represents a readonly property of type Instant, 
    // >with< a backing field, whose value is set during 
    // construction (specifically during execution of the 
    // primary constructor) to the current time. It uses 
    // the default getter.
    val timeOfConstruction: Instant = Instant.now()
    
    // This represents a readonly property of type Instant, 
    // >without< a backing field, whose value is calculated 
    // whenever it is accessed (i.e. whenever the getter is 
    // called). It defines a custom getter
    val timeOfAccess: Instant get() = Instant.now()
}

fun main() {
    val timeOfStuff = TimeOfStuff()

    val timeOfConstruction1 = timeOfStuff.timeOfConstruction
    val timeOfAccess1 = timeOfStuff.timeOfAccess
    
    Thread.sleep(1000)

    val timeOfConstruction2 = timeOfStuff.timeOfConstruction
    val timeOfAccess2 = timeOfStuff.timeOfAccess

    println(timeOfConstruction1.epochSecond == timeOfConstruction2.epochSecond)
    println(timeOfAccess1.epochSecond != timeOfAccess2.epochSecond)
}
//sampleEnd

In the above, also note how Kotlin automatically translates Java properties into Kotlin properties. The actual method defined on the Instant class is public long getEpochSecond(). This is done with all Java code called from Kotlin.

While fields and properties are almost exclusively discussed in the context of classes, the definitions at the top are not bound to the concept of a class. Indeed, Kotlin also permits properties defined at the top-level, outside of classes:


//sampleStart
import java.time.Instant

val formattedTimestamp = "The time is:"
    get() = "$field ${Instant.now()}"

class Test {
    fun getNow() = formattedTimestamp
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the architect of code's ballet,
        With sealed classes, it pirouettes away.
        In the world of programming, a dance so fine,
        With Kotlin, your code will shine!
    """.trimIndent()
    println(poem)
}

However, it is not possible to define properties with custom getters/setters inside functions. When used inside functions, val and var declare variables, which contain some value, and not properties (a pair of functions).

Exercises

Now that we know how properties behave, we are ready to write a mutable version of Person from a previous lesson.

Rewrite this class in Kotlin:


//sampleStart
public class Person {
    private String title;
    private String firstName;
    private String middleName;
    private String lastName;

    public Person(
            String title, 
            String firstName, 
            String middleName, 
            String lastName
    ) {
        this.title = title;
        this.firstName = firstName;
        this.middleName = middleName;
        this.lastName = lastName;
    }

    public Person(String firstName, String lastName) {
        this("", firstName, "", lastName);
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(final String newTitle) {
        title = newTitle;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(final String newFirstName) {
        firstName = newFirstName;
    }

    public String getMiddleName() {
        return middleName;
    }

    public void setMiddleName(final String newMiddleName) {
        middleName = newMiddleName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(final String newLastName) {
        lastName = newLastName;
    }

    public static class Builder {
        private String title = "";
        private String firstName = "";
        private String middleName = "";
        private String lastName = "";

        public void setTitle(final String newTitle) {
            title = newTitle;
        }

        public void setFirstName(final String newFirstName) {
            firstName = newFirstName;
        }

        public void setMiddleName(final String newMiddleName) {
            middleName = newMiddleName;
        }

        public void setLastName(final String newLastName) {
            lastName = newLastName;
        }

        public Person buildPerson() {
            return new Person(
                    title, 
                    firstName,
                    middleName,
                    lastName
            );
        }
    }
}
//sampleEnd

import org.junit.Assert
import org.junit.Test

class Test {
    @Test fun testPerson() {
        PersonKotlin(
            firstName = "Thomas",
            middleName = "Michael",
            lastName = "Shelby"
        ).apply {
            Assert.assertTrue("Firstname is not implemented correctly", firstName == "Thomas")
            Assert.assertTrue("MiddleName is not implemented correctly", middleName == "Michael")
            Assert.assertTrue("LastName is not implemented correctly", lastName == "Shelby")
        }

        PersonKotlin(
            title = "Mr.",
            lastName = "Bean"
        ).apply {
            Assert.assertTrue("Title is not implemented correctly", title == "Mr.")
        }

        PersonKotlin().apply {
            title = "Mrs."
            firstName = "Jane"
            middleName = "Jen"
            lastName = "Doe"

            Assert.assertTrue("Title is not mutable", title == "Mrs.")
            Assert.assertTrue("Firstname is not mutable", firstName == "Jane")
            Assert.assertTrue("MiddleName is not mutable", middleName == "Jen")
            Assert.assertTrue("LastName is not mutable", lastName == "Doe")
        }

    }
}

//sampleStart
/**
 * Adapt the essence of the Person.java class to the Kotlin world.
 */
class PersonKotlin
//sampleEnd

Solution

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer