As a Java developer, the concept of a backing field is a bit foreign to me. Given:
class Sample {
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
}
What's this backing field good for? Kotlin docs said: Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using custom accessors. Why? Whats the difference with using the properties name itself inside the setter, eg.
class Sample {
var counter = 0
set(value) {
if (value >= 0) this.counter = value // or just counter = value?
}
}
Because, say if you don't have field
keyword, you won't be able to actually set/get the value in the get()
or set(value)
. It enables you to access the backing field in the custom accessors.
This is the equivalent Java code of your sample:
class Sample {
private int counter = 0;
public void setCounter(int value) {
if (value >= 0) setCounter(value);
}
public int getCounter() {
return counter;
}
}
Apparently this is not good, as the setter is just an infinte recursion into itself, never changing anything. Remember in kotlin whenever you write foo.bar = value
it will be translated into a setter call instead of a PUTFIELD
.
EDIT: Java has fields while Kotlin has properties, which is a rather higher level concept than fields.
There are two types of properties: one with a backing field, one without.
A property with a backing field will store the value in the form of a field. That field makes storing value in memory possible. An example of such property is the first
and second
properties of Pair
. That property will change the in-memory representation of Pair
.
A property without a backing field will have to store their value in other ways than directly storing it in memory. It must be computed from other properties, or, the object itself. An example of such property is the indices
extension property of List
, which is not backed by a field, but a computed result based on size
property. So it won't change the in-memory representation of List
(which it can't do at all because Java is statically typed).
Initially, I too had a tough time understanding this concept. So let me explain it to you with the help of an example.
Consider this Kotlin class
class DummyClass {
var size = 0;
var isEmpty
get() = size == 0
set(value) {
size = size * 2
}
}
Now when we look at the code, we can see that it has 2 properties i.e - size
(with default accessors) and isEmpty
(with custom accessors). But it has only 1 field i.e. size
. To understand that it has only 1 field, let us see the Java equivalent of this class.
Go to Tools -> Kotlin -> Show Kotlin ByteCode in Android Studio. Click on Decompile.
public final class DummyClass {
private int size;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.size == 0;
}
public final void setEmpty(boolean value) {
this.size *= 2;
}
}
Clearly we can see that the java class has only getter and setter functions for isEmpty
, and there is no field declared for it. Similarly in Kotlin, there is no backing field for property isEmpty
, since the property doesn't depend on that field at all. Thus no backing field.
Now let us remove the custom getter and setter of isEmpty
property.
class DummyClass {
var size = 0;
var isEmpty = false
}
And the Java equivalent of the above class is
public final class DummyClass {
private int size;
private boolean isEmpty;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.isEmpty;
}
public final void setEmpty(boolean var1) {
this.isEmpty = var1;
}
}
Here we see both the fields size
and isEmpty
. isEmpty
is a backing field because the getter and setter for isEmpty
property depend upon it.
Backing fields are good for running validation or triggering events on state change. Think of the times you've added code to a Java setter/getter. Backing fields would be useful in similar scenarios. You would use backing fields when you needed to control or have visibility over setters/getters.
When assigning the field with the field name itself, you're actually invoking the setter (i.e. set(value)
). In the example you have, this.counter = value
would recurse into set(value) until we overflow our stack. Using field
bypasses the setter (or getter) code.
My understanding is using field identifier as a reference to the property's value in get or set, when you want to change or use the property's value in get or set.
For example:
class A{
var a:Int=1
get(){return field * 2} // Similiar to Java: public int geta(){return this.a * 2}
set(value) {field = value + 1}
}
Then:
var t = A()
println(t.a) // OUTPUT: 2, equal to Java code: println(t.a * 2)
t.a = 2 // The real action is similar to Java code: t.a = t.a +1
println(t.a) // OUTPUT: 6, equal to Java code: println(t.a * 2)