1. Introduction

In this tutorial, we’re going to look at properties in Kotlin and how to access them. Properties are similar to fields in Java, but there are some important differences.

For example, properties have auto-generated getters and setters. They can also be declared at the top-level package scope – they don’t have to belong to a class.

2. Getters and Setters For Properties

In Kotlin, a property doesn’t require explicit getter or setter methods:

var author: String = "Frank Herbert"

This is the same as defining the following get() and set() methods:

var author: String = "Frank Herbert"
    get() {
        return field
    }
    set(value) {
        field = value
    }

The default getter and setter is a familiar pattern we see in Java, but in Kotlin, we don’t have to create a private backing field for the property.

We can use dot syntax to call the getter and setter for class properties:

val book = Book()
print(book.author) // prints "Frank Herbert"
book.author = "Kurt Vonnegut"
print(book.author) // prints "Kurt Vonnegut"

Now that we understand the basic functionality of properties let’s look at some ways of modifying them.

2.1. Accessing the Backing Field

Every property we define is backed by a field that can only be accessed within its get() and set() methods using the special field keyword. The field keyword is used to access or modify the property’s value. This allows us to define custom logic within the get() and set() methods:

var rating: Int = 5
    get() {
        if (field < 5) {
            print("Warning: this is a terrible book")
        }
        return field
    }
    set(value) {
        field = when {
            value > 10 -> 10
            value < 0 -> 0
            else -> value
        }
    }

Defining a custom getter or setter allows us to perform any number of useful operations like input validation, logging, or data transformations. By adding this business logic directly to the getter or setter, we ensure that it’s always performed when the property is accessed.

Try to avoid or minimize side-effects in the getter and setter methods as much as possible. It makes our code harder to understand.

2.2. val vs. var

If we want to be able to modify a property’s value, we mark it with the var keyword. If we want an immutable property, we mark it with a val keyword. The main difference is that val properties can’t have setters.

A consequence of defining custom getters is that a val property can actually change its value. For example, the isWorthReading property uses the mutable rating property to compute its value:

val isWorthReading get() = this.rating > 5

In this sense, the property acts as a method when using a custom getter. If we need to do an expensive or slow operation to compute the property, it would be better to use a method to provide clarity. Developers may be unaware when using the property that it’s not just retrieving a value from memory.

2.3. Visibility Modifiers

In Java, we often want class fields to have public read access and private write access. Using a public getter method and a private or protected setter method achieves this. Kotlin provides a succinct way to implement this access pattern by allowing visibility modifiers on a property’s set() method:

var inventory: Int = 0
    private set

Now any consumers of the book class can read the inventory property, but only the Book class can modify it. Note that the default visibility for properties is public.

The getter will always have the same visibility as the property itself. For example, if the property is private, the getter is private.

3. Java Interoperability

When calling Java code from Kotlin, the compiler will automatically convert Java fields into properties if the getters and setters are properly named. If a Java class has a field accessed by a no-argument method that starts with “get” and modified with a single-argument method that starts with “set”, it becomes a var property in Kotlin.

For example, the getLevel() and setLevel() methods of the Logger class in Java’s logging library:

val logger = Logger.getGlobal()
logger.level = Level.SEVERE 
print(logger.level) // prints "SEVERE"

If the field only has a public getter and no public setter, it becomes a val property in Kotlin. For example, the name field of the Logger class:

print(logger.name) // prints "global"
logger.name = "newName" // causes a compiler error

4. Conclusion

In this tutorial, we looked into getters and setters for properties. They provide powerful and concise functionality through optional get() and set() methods. We must be careful not to perform expensive computations in these methods and to minimize side-effects to keep our code clean and understandable.

The code examples used in this tutorial are available over on GitHub.

guest
0 Comments
Inline Feedbacks
View all comments