Programming Kotlin Applications. Бретт Мак-Лахлин
Читать онлайн книгу.passing in a decimal for height—in this case 68.2—which seems right. Here's the constructor in
Person
:
class Person(var firstName: String, var lastName: String, var height: Float, var age: Int, var hasPartner: Boolean) {
So what gives? The problem is that Kotlin is not converting types. A few things are actually happening here:
1 You give Kotlin the value 68.2. Kotlin sees a decimal number and automatically uses the Double type. This is important! Anytime a decimal is not given an explicit type, Kotlin will use Double.
2 The Double is passed into the Person constructor. However, Person is expecting a Float.
3 Kotlin will not try to convert between these two types—even though, in this case, they are compatible! Instead, Kotlin insists on strong typing, and throws an error.
You Can Explicitly Tell Kotlin What Type to Use
You have two ways to fix this. The easiest is to tell Kotlin that you want the value you're passing in to be treated as a Float
; you can do that by putting a capital F
after the number:
val brian = Person("Brian", "Truesby", 68.2F, 33, true) println(brian) // Create another person val rose = Person("Rose", "Bushnell", 56.8F, 32, true) println(rose)
In this case, you're sending Person
what it wants: a Float
. That passes the type checking and things are OK again.
Try to Anticipate How Types Will Be Used
While that solution works, it's a bit inelegant. It is going to require every user of your Person
class—you included—to add that F
to decimals or to use the Float
type for variables passed into a constructor. And, as you just learned, Kotlin uses Double
as the default decimal type.
A better solution is to realize that if Kotlin defaults to Double
, maybe you should, too. (At least, unless you have a really good reason not to.) So change your Person
constructor to take in what most users will pass—a Double
created from a typed-in number:
class Person(var firstName: String, var lastName: String, var height: Double, var age: Int, var hasPartner: Boolean) {
Easy enough! And, because you've anticipated how users will create a new instance of Person
, you'll annoy your friendly developer comrades a lot less.
NOTE Don't forget to remove the F
from the two numbers in your main
function. If you don't, you'll get another error—this time from trying to pass a Float
into a constructor that expects a Double
.
It's Easy to Break Kotlin (Sort of)
The title of this chapter is “It's Hard to Break Kotlin,” which may seem like the opposite of what you're seeing. Without much effort, you've managed to get Kotlin to throw a lot of different errors, and Kotlin seems quite resistant to helping you out with converting between types.
That said, Kotlin is actually breaking before your code is running in some production system, serving an API for a mobile app, or running on a phone or just handling web requests. In other words, Kotlin is making you do some extra work at compile time—when you're writing code—to avoid potential problems when the program is running and needs to work.
Put another way, the chapter might be better titled, “It's Hard to Break Kotlin When It's Running in Production Because It's Going to Make You Be Really Specific about Types When You're Writing that Code Before It Gets to Production.” Of course, that's really not a title that anyone is going to let get through editing and proofing, so let's go with “It's Hard to Break Kotlin” and trust that you know what that really means.
OVERRIDING PROPERTY ACCESSORS AND MUTATORS
With what you know about types, and strong types in particular, you're finally ready to get back to that annoying little detail of Person
: if you set the last name outside the Person
constructor, printing the class instance gives the original last name, not the modified one. As a refresher, here's the code in question:
// Create another person val rose = Person("Rose", "Bushnell", 56.8, 32, true) println(rose) // Change Rose's last name rose.lastName = "Bushnell-Truesby" println(rose)
The result? This:
Rose Bushnell Rose Bushnell
What really needs to happen here? Well, every time that a last name—or first name, for that matter—is updated, the full name of the person that the instance represents needs to be updated. Seems simple enough, right?
Well, hold on, because this is going to get fairly complicated really fast. You're about to enter the somewhat unusual world of overriding mutators and accessors in Kotlin.
Custom-Set Properties Can't Be in a Primary Constructor
What a mouthful! It's true, though: because we want to take control of what happens when a first or last name is changed, we're going to have to make a lot of changes to how Person
is constructed. Basically, we have to override the accessor for firstName
and lastName
. To do that, we cannot have those properties initialized in the Person
primary constructor.
NOTE You may have noticed the term “primary constructor” here. Think of that simply as “the constructor” for now. Later, you'll see that Kotlin lets you define multiple constructors, and the primary one is the one defined on the first line of the class—the one you already have.
More on this later, though, so don't worry too much about primary and secondary constructors for now.
Move Properties Out of Your Primary Constructors
You've actually just stumbled onto a fairly standard best practice in Kotlin: it's often a good idea to move property definitions out of the primary constructor. You'll recall that you started like this way back in Chapter 1:
class Person(firstName: String, lastName: String) {
Then, before you added other properties, you added the var
keyword to each listed data input:
class Person(var firstName: String, var lastName: String) {
This made firstName
and lastName
properties, with automatically generated accessors and mutators. The problem is that, while expedient at the time, this has now come back to bite you.
To undo this, simply remove var
from all of the properties in the current version of the Person
constructor:
class Person(firstName: String, lastName: String, height: Double, age: Int, hasPartner: Boolean) {
Now try to run your PersonApp
, and you're going to see an error discussed earlier:
Error:(13, 10) Kotlin: Unresolved reference: lastName
You should recall that because lastName
is not a property anymore (because there's no var