Название: Programming Kotlin Applications
Автор: Бретт Мак-Лахлин
Издательство: John Wiley & Sons Limited
Жанр: Программы
isbn: 9781119696216
isbn:
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 assigned the value passed into the mutator—in our example, that's the String
“Bobby.”
The final piece here is understanding why you can't say something like this:
var firstName: String = _firstName set(value) { firstName = value }
This will actually compile, but it will give you a horrible error when you try to run and use this class. The problem is that when Kotlin sees this:
firstName = value
it interprets that as “run the code that mutates firstName
.” But that code is the code already running. So it
СКАЧАТЬ