Kotlin Properties - mariamaged/Java-Android-Kotlin GitHub Wiki
Kotlin - Properties
class Foo {
var count = 0
fun something() {
count += 1
println("something() was called $count times")
}
}
val foo = Foo()
foo.something()
foo.something()
foo.something()
- You can have var and val
labelled properties
in your classes, just like you can outside of classes. - Properties in classes are accessible by instances of those classes, just
something()
function inFoo
class has access to thecount
property.
Initialization
Properties need to be initialized at some point.
When Declared
- Much of the time, you will initialize the properties when you declare with them.
- Primarily, such initialization will refer to:
- Literals.
- Constructor parameters.
- Properties declared outside of classes.
- And perhaps constants.
- Other properties that themselves are initialized.
Here, we initialize count
based on sillyCount
constructor parameter:
class Foo(val sillyCount: String) {
var count = sillyCount.toInt()
fun something() {
count += 1
println("something() was called $count times")
}
}
val foo = Foo()
foo.something()
foo.something()
foo.something()
In an Init Block
- You can also initialize properties in an init block.
- Here, we initialize count with 0, then replace it immediately with a value derived from
sillyCount
. - As it turns out, the initialization is not required.
class Foo(val sillyCount: String) {
var count = 0
init {
count = sillyCount.toInt()
}
fun something() {
count += 1
println("something() was called $count times")
}
}
val foo = Foo()
foo.something()
foo.something()
foo.something()
- This is just as valid and gives the same result.
class Foo(val sillyCount: String) {
var count: Int;
init {
count = sillyCount.toInt()
}
fun something() {
count += 1
println("something() was called $count times")
}
}
val foo = Foo("7")
foo.something()
foo.something()
foo.something()
Sometimes Later
It may be that you do not know how to initialize the property now, but you will initialize it later.
- For example, in
Android app development
, there will be properties that you cannot initialize until some particular point in an object's lifecycle, such as inonCreate()
of an Activity or Fragment.
- The most common way of handling this is via lateinit.
- lateinit: is a promise that you will not attempt to reference a property until you initialize it "late" (i.e., sometime after the object is initialized).
- Here, we have both a count and a label, where we use both in the printed output.
- For some reason, we do not have the value for label at the time we create an instance of Foo.
- So, we define late as lateinit, so we can
skip the initialization
.
- The implied requirement is that
defineTag()
must be calledbefore
something()
. - We need to initialize label before we can refer to it safely.
- Failing to do this - such as by commenting out the foo.defineTag("Foo") line in the sample - results in a crash.
- NOTE: lateinit cannot be used for
types based on primitives
, so you cannot have a lateinit Int, Boolean, etc.
class Foo {
var count = 0
lateinit var label: String
fun defineTag(tag: String) {
label = tag
}
fun something() {
count += 1
println("$label: something() was called $count times")
}
}
val foo = Foo()
foo.defineTag("Foo")
foo.something()
foo.something()
foo.something()
Eh, Whatever
- Yet another possibility is to use by lazy:
class Foo(val rawLabel: String) {
var count = 0
val label: String by lazy { rawLabel.toUpperCase() }
fun something() {
count += 1
println("$label: something() was called $count times"
}
}
val foo = Foo()
foo.something()
foo.something()
foo.something()
- Here, label is initialized lazily.
- Whenever we first attempt to use label, the
lamda expression
will beexecuted
, and that value is what we see as the value of label. - In this case, the lamda expression takes a constructor parameter and converts it to all caps, so our output is:
FOO: something() was called 1 times
FOO: something() was called 2 times
FOO: something() wad called 3 times
- In truth, we would not need to use by lazy here.
- Since the constructor parameter:
- Is available at initialization time.
- It is not changing. we could just initialize label normally.
class Foo(val rawLabel: String) {
var count = 0
val label = rawLabel.toUpperCase()
fun something() {
count += 1
println("$label: something() was called $count times")
}
}
- As with lateinit var, by lazy is good for cases where you cannot initialize the val up front, for whatever reason.
- (e.g., onCreate() has not yet been called on your Android Activity).
- And as with lateinit var, you run the risk of crashing if the conditions are not ready for your lamda expression to be evaluated.
The difference amounts to "push" versus "pull".
- With lateinit var, we try to initialize the property as soon as it is practical,
pushing
a value into it.- With by lazy, we hope that we do not try referencing it before the lamda expression will work...but once it is referenced, we
pull
its initial value from other data, using the lamda expression to derive the value that we want to use.
Fake Properties
- Another way to initialize a property...is to have it only look to outsiders like it is a regular property.
We usually distinguish between accessing a property and calling a function by the accessing of existence of parentheses.
- anObject.someProperty references a
property
.- anObject.thstFunction() references a
function
.
Constructor Parameters Sans var Or val
- Before, we saw how to define constructor parameters that are
val
orvar
properties.
class Foo(var count: Int) {
fun something() {
count += 1
println("somrthing() was called $count times")
}
}
val foo = Foo(0)
foo.something()
foo.something()
foo.something()
- Technically, the
val
orvar
keyword isoptional
. - However, PLAIN constructor parameters are
not accessible
fromordinary functions
. - They
are accessible
when initializing properties, either:- In direct initializer expressions.
- Via init blocks.
class Foo(initialCount: Int) {
var count = 0
fun something() {
count += 1
println("something() was called $count times")
}
}
val foo = Foo(0)
foo.something()
foo.something()
foo.something()
- We cannot refer to initialCount in
something()
- that would result in an unresolved reference: initialCount compiler error.
Constants
- As with Java, Kotlin has
const
keyword, which you can use to declare certainval
properties as constants.
Getting Down in the Weeds
- In Java, we have fields.
class Foo {
private int myField;
}
We make the comparison to Kotlin properties, but that is only an approximation.
- Often times, in Java, sometimes we define
"getter"
and"setter"
methods, and sometimes those adhere to"JavaBean"
naming conventions.
class Foo {
private int myField;
int getMyField() {
return myField;
}
void setMyField(int newValue) {
myField = newValue;
}
}
- A Kotlin property really represents the combination of these three elements:
- A field.
- A getter function.
- A setter function.
- It so happens that for simple properties - like those we have seen in this chapter and previously -
those three elements
havedefault implementations
that arehidden
between the simplevar-style declaration
. - When we
read
a property's value, we really are calling a getter function that returns the value of the property's "backing field". - When we
write
a property's value, we really are calling a setter function that sets the value of the backing field.