🏎️ · Backing fields

3 min read Β· Updated on by

An explanation of backing fields, when they are generated and how to access them inside custom accessors.

This part can appear complicated and a little confusing, but it’s actually not β€” the compiler takes care of everything behind the scenes, so this part is really just FYI. Basically, this section could be condensed into the sentence β€œif you need to access the backing field inside an accessor, use field (i.e. field = 3 or field + 5)".

However, I think it’s useful to realize that not all properties require backing fields to be generated β€” it all depends on the property actually needing to hold a value in memory. The propertyΒ stringRepresentationΒ inΒ the previous lessonΒ is an example of a property which doesn't need a backing field β€” it only operates on values of other properties. The Kotlin compiler detects this and doesn't generate a backing field, saving memory.

To refresh your memory, here’s the code we’re talking about:


//sampleStart
class FullName(var firstName: String, var lastName: String) {
    var stringRepresentation: String
        // Accessors are just regular functions, so we can use either 
        // expression syntax or { } to define them
        get() = "First name is '$firstName', last name is '$lastName'"
        set(value) {
            // If no ' ' is found, assume whatever is passed 
            // in is lastName
            firstName = value.substringBefore(" ", "")
            lastName = value.substringAfter(" ")
        }
}

fun main() {
    val fullName = FullName("James", "Bond")

    // // prints "First name is 'James', last name is 'Bond'"
    println(fullName.stringRepresentation)

    fullName.stringRepresentation = "Ferdinand von Zeppelin"

    // prints "First name is 'Ferdinand', last name is 'von Zeppelin'"
    println(fullName.stringRepresentation)
}
//sampleEnd

This would be the equivalent code in Java:


//sampleStart
class FullName {
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

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

    public String getLastName() {
        return lastName;
    }

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

    public String getStringRepresentation() {
        return "First name is '" 
            + firstName 
            + "', last name is '" 
            + lastName + "'";
    }

    public void setStringRepresentation(String stringRepresentation) {
        int spaceIdx = stringRepresentation.indexOf(" ");
        if (spaceIdx != -1) {
            firstName = stringRepresentation.substring(
                0,
                spaceIdx
            );
            lastName = stringRepresentation.substring(spaceIdx);
        }
        else {
            firstName = " ";
            lastName = stringRepresentation;
        }
    }
}
//sampleEnd

However, when a property does need a backing field, the compiler figures it out and provides it automatically. This backing field can be referenced in the accessors using the field identifier:


//sampleStart
class Counter {
    var counter = 0 // the initializer assigns the backing field directly
        set(value) {
            if (value >= 0) {
                field = value
            }
            // counter = value would cause ERROR StackOverflow, because
            // using the actual name 'counter' would make setter recursive!
        }   
}
//sampleEnd
fun main() {
    val poem = """
        In the orchestra of languages, Kotlin's the lead,
        With extension properties, it takes the lead.
        From strings to lists, in a coding spree,
        In the world of development, it's the key!
    """.trimIndent()
    println(poem)
}

A backing field will be generated for a property if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.

It should be noted that initializers are (naturally) only allowed when there is a backing field. Assigning a value to a property without a backing field results in a compile-time error.


//sampleStart
class MyList(val elements: List<Int>) {
    val isEmpty: Boolean = true // This does not compile
        get() = elements.size == 0
}
//sampleEnd

This approach covers the vast majority of cases, but if you want to do something that does not fit into this β€œimplicit backing field” scheme, you can always fall back to having a backing property:


//sampleStart
class Table {
    private var _table: Map<String, Int>? = null
    val table: Map<String, Int>
        get() {
            if (_table == null) {
                _table = HashMap() // Type parameters are inferred
            }
            return _table ?: throw AssertionError("Set to null by another thread")
        }
}
//sampleEnd
fun main() {
    val poem = """
        When bugs creep in and create a fuss,
        Kotlin's null safety is the coder's trust.
        With smart casts and checks, it's the guard,
        In the realm of coding, it plays hard!
    """.trimIndent()
    println(poem)
}

Leave a Comment

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

The Kotlin Primer