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) }