Programming Kotlin Applications. Бретт Мак-Лахлин
keyword in the constructor definition), Kotlin does not create accessors or mutators. That means that this line in
main
now won't work:
rose.lastName = "Bushnell-Truesby"
So there's a bit of an apparent conflict that has arisen:
You can't customize accessors or mutators if a property is declared in a class's constructor.
If a property isn't declared in a class's constructor, and it's not declared elsewhere in the class, you don't get an automatic accessor or mutator.
Initialize Properties Immediately
Now, just to make this a bit more complicated, there's another rule you've run across, too:
Any property not defined in a constructor must be initialized on instance creation.
To remember how this shows up, go ahead and define all the properties declared in your constructor, but this time do it just under the constructor line, similar to how you defined lastName
:
class Person(firstName: String, lastName: String, height: Double, age: Int, hasPartner: Boolean) { var fullName: String var firstName: String var lastName: String var height: Double var age: Int var hasPartner: Boolean // Set the full name when creating an instance init { fullName = "$firstName $lastName" }
Compile, run, and you'll get another error you've seen before, but this time with all of your properties:
Error:(6, 5) Kotlin: Property must be initialized or be abstract Error:(7, 5) Kotlin: Property must be initialized or be abstract Error:(8, 5) Kotlin: Property must be initialized or be abstract Error:(9, 5) Kotlin: Property must be initialized or be abstract Error:(10, 5) Kotlin: Property must be initialized or be abstract
This cropped up with firstName
, and we fixed it by setting that value in your code's init
block. We need to do something somewhat similar here: we need to assign to each new property the value that is passed into the constructor. So the property firstName
should be given the value passed into firstName
that's declared as an input to the Person
constructor.
NOTE If that sentence seemed confusing, you're in good company. Two firstName
pieces of information are floating around here: the one passed into the constructor, and the property name. That's a problem we'll fix shortly.
Kotlin gives you a way to this assignment but it's going to look a little odd. Basically, the values that come into your constructor are available to your properties at creation time, and you can assign them directly to those properties. That's a little easier said than described, so take a look at the new version of Person
shown in Listing 2.5.
LISTING 2.5: Assigning constructor information to properties
package org.wiley.kotlin.person class Person(firstName: String, lastName: String, height: Double, age: Int, hasPartner: Boolean) { var fullName: String var firstName: String = firstName var lastName: String = lastName var height: Double = height var age: Int = age var hasPartner: Boolean = hasPartner // Set the full name when creating an instance init { fullName = "$firstName $lastName" } override fun toString(): String { return fullName } }
Take just the line dealing with firstName
:
var firstName: String = firstName
A property is declared, and it's both read and write, because var
is used. (Remember, if you wanted this to be a read-only property, you'd use val
.) That property is given a name— firstName
—and then a type, String
. Then, the new property is assigned a value. In this case, that value is whatever is passed into the constructor through firstName
.
You can now compile your code and run it, and it works again! (Although there's still that last name issue to fix. But you're getting closer.)
Try to Avoid Overusing Names
This code is still pretty confusing, though. You have properties named the same as inputs to your constructor, which is legal, but a pain to understand and follow. One easy solution, and a common one in the Kotlin community, is to use an underscore for non–property value names passed into constructors. Listing 2.6 shows this change; it's purely cosmetic but really cleans up the readability of the class.
LISTING 2.6: Clarifying property names and constructor inputs
package org.wiley.kotlin.person class Person(_firstName: String, _lastName: String, _height: Double, _age: Int, _hasPartner: Boolean) { var fullName: String var firstName: String = _firstName var lastName: String = _lastName var height: Double = _height var age: Int = _age var hasPartner: Boolean = _hasPartner // Set the full name when creating an instance init { fullName = "$firstName $lastName" } override fun toString(): String { return fullName } }
NOTE Because Kotlin is so strongly typed and has such unique functionality—multiple constructors, properties versus method inputs, and more—using these coding best practices will really help separate your code from the code of those who are less experienced and savvy.
Override Mutators for Certain Properties
You're now finally ready to do what we set out to do: override how changing a first name or last name works. First, you need to see how a typical mutator is defined.
Here's some more rather Kotlin-specific code that defines a custom mutator; in this case, it's for firstName
:
var firstName: String = _firstName set(value) { field = value }
The set
keyword tells Kotlin that this is a mutator. Additionally, Kotlin assumes that the mutator is for whatever property was just defined.
WARNING Kotlin uses a property's definition line in a file to determine that, if there's a custom mutator (or accessor), that definition is on the very next line. That's a big deal, and worth noting.
Then, value
represents the value coming into the mutator. So in the following example, value
would come to the firstName
mutator as “Bobby”:
// Change Brian's first name brian.firstName = "Bobby"
Now here's where things look a little odd. There's this line:
field = value
What's going on there? What the heck is field
? Well, it's the property being mutated. It's what's called a backing field. So here, field
references the backing field for the property, which is firstName
(because that's the property just defined). That backing field is