1. Overview

In this quick tutorial, we’re going to get familiar with backing fields and their use cases in Kotlin.

First, we’ll start with a short introduction to properties and custom accessors in Kotlin. Then we’ll use backing fields in a custom accessor example. Finally, we’ll see how backing fields are implemented at the bytecode level.

2. Backing Fields

Kotlin supports declaring mutable or immutable properties. All we have to do is to mark the property with val or var:

data class HttpResponse(val body: String, var headers: Map<String, String>)

In the above example, Kotlin compiler generates a getter for the body property, and both a getter and setter for the headers property. Under the hood, Kotlin will use a Java field to store the property values. These Java fields are known as backing fields in the Kotlin world.

Sometimes, however, we might need to create custom accessors to control their logic. For instance, here we’re creating a simple getter for the hasBody property:

data class HttpResponse(val body: String, var headers: Map<String, String>) {
    val hasBody: Boolean
        get() = body.isNotBlank()
}

The getter for hasBody returns true if the body is blank. Since Kotlin can compute the hasBody value from the body property, it won’t generate a backing field for it.

Now let’s suppose we’re going to set the statusCode property if and only if the given value is between 100 and 599. At our first attempt, we might try something like:

var statusCode: Int = 100
    set(value) {
        if (value in 100..599) statusCode = value
    }

Every time we set the statusCode property, Kotlin will call the set(value) custom accessor. The current implementation is an endless recursive call since we’re also setting this field inside the setter itself.

To avoid this endless recursion, we can use the backing field that Kotlin generates for this property:

var statusCode: Int = 100
    set(value) {
        if (value in 100..599) field = value
    }

As shown above, when a property needs its backing field, Kotlin provides it automatically. Moreover, we can reference the backing field inside custom accessors via the field identifier.

Put simply, the backing field is where the value for a property will be stored. In the last example, the backing field helped us to avoid the recursion issue.

3. Bytecode Representation

Kotlin will generate a backing field for a property if we use at least one default accessor or we reference the field identifier inside a custom accessor. Default accessors are those that are generated with val or var keywords.

To verify this statement, we can take a peek at the generated bytecode. First, let’s compile the Kotlin code using kotlinc:

$ kotlinc BackingField.kt

Now, we can inspect the bytecode using javap:

$ javap -c -p com.baeldung.backingfield.HttpResponse 
Compiled from "BackingField.kt"
public final class com.baeldung.backingfield.HttpResponse {
  private int statusCode;
  private final java.lang.String body;
  private java.util.Map<java.lang.String, java.lang.String> headers;
  // truncated

As shown above, Kotlin generates the backing field for three properties:

  • The body property since it’s only using the default accessors
  • The headers property for the same reason
  • And the statusCode property since it references the field identifier

However, because the hasBody property doesn’t meet either of those conditions, Kotlin didn’t generate a backing field for it. Consequently, there is no sign of such a field in the generated bytecode.

4. Conclusion

In this short tutorial, we had a quick refresher about properties and custom accessors in Kotlin. After that, we got familiar with backing fields in Kotlin and their use cases, as well as their bytecode representation.

As usual, all the examples are available over on GitHub.


« 上一篇: Kotlin return标签用法
» 下一篇: Kotlin中的聚合操作