If you just started with Kotlin, there’s a good chance that you are overwhelmed with the new possibilities.
This is especially true if you originally came from Java, where concepts like Properties or Function Types simply do not exist.
In this post, I will address some of the questions concerning Properties a beginner might have when starting with Kotlin.
Properties and Fields
Unlike Java, Kotlin has no fields (well, technically Kotlin does have fields, but conceptionally it doesn’t).
Consider the two examples:
public class Banana {
public String color = "green"
}
class Banana {
var color = "green"
}
On the first glance, they seem equivalent. But they aren’t.
Kotlin has no fields and color
can therefore not be accessed directly but will be hidden behind getColor
and setColor
methods (so called accessor methods). These methods will be hidden if we write pure Kotlin code, but they are still there for interoperability with java.
Accessor methods are generated for us by the Kotlin compiler. But we can write the getColor
and setColor
methods ourselves, if we want to.
class Banana {
var color = "green"
get() = field
set(value) {field=value}
}
Writing it like this is equivalent to the previous example, where we left out the get()
and set()
methods. The field
identifier references the backing field of this property. These backing fields are used to store the actual value of a property and are automatically generated if we use the default implementation of get()
or set()
or if we write a custom accessor and use the field
identifier.
Writing the accessors ourselves is pointless unless we want to do intercept the access of the property.
Maybe we only want to allow certain values:
class Banana {
var color = "green"
set(value) {
if(value == "yellow" || value == "green"){
field = value
}
}
}
With this, the property (that is, the backing field) will not change unless the new value is either "yellow"
or "green"
.
Read-only Properties vs. “Normal” Properties
As mentioned above, a backing field will not be generated unless we actually use it (be it directly by referencing field
or indirectly by using a default accessor).
Let’s change the example a bit so that it’s not using a backing field:
class Banana {
var ripeness = 1
var color: String = "green"
get() = when {
ripeness > 80 -> "brown"
ripeness > 50 -> "yellow"
else -> "green"
}
}
I added a new mutable property ripeness
. The old color
property will now return a different result, depending on the value stored in ripeness
. Because color
is declared as var
, and therefore a default set
is generated, it will still generate a backing field.
This backing field is really useless though:
val banana = Banana()
banana.color = "blue"
println(banana.color)
The println(banana.color)
will never print "blue"
. It will call the get
-function and there fore return "green"
.
Since we don’t want useless backing fields (it’s a bit obscure and needs memory) we get rid of the setter by declaring our property as val
, which means as read-only.
class Banana {
var ripeness = 1
val color: String
get() = when {
ripeness > 80 -> "brown"
ripeness > 50 -> "yellow"
else -> "green"
}
}
This val
property will not offer a set
method.
Read-only Properties vs. Functions
Writing a property like this is essentially the same as writing a function.
class Banana {
var ripeness = 1
fun getColor(): String = when {
ripeness > 80 -> "brown"
ripeness > 50 -> "yellow"
else -> "green"
}
}
But I prefer the property syntax. A rule of thumb can be “if it describes the object, it’s a property. If does something with the object or with another object, it’s a function”.
I think the C# explanation on “Choosing Between Properties and Methods” is useful for Kotlin as well.
The official style guide also has a section about this.
A Possible Pitfall
With many new possibilities it easy to run into problems. One problem I had was forgetting the get
for a property.
class Banana {
var ripeness = 1
val color: String = when {
ripeness > 80 -> "brown"
ripeness > 50 -> "yellow"
else -> "green"
}
}
While this is syntactically correct, it is not what we actually want.
By omitting the get
accessor, we assign the value of the backing field. This assignment takes place during the construction of our Banana
object. This means that the result will be computed once and then never again. Since the ripeness
is initially set to 1
, calling banana.color
will always return "green"
.
Learn more
The Properties page is a good starting point, as well as Visibility Modifiers, as they can also be applied to properties.
Some more advanced property topics include Overriding Properties and Delegated Properties