Programming Kotlin Applications. Бретт Мак-Лахлин
Terminology Update: Functions and Methods
A few specifics on terminology again. A function is a piece of code that runs. main
is an example of a function. A method is, basically, a function that's attached to an object. Put another way, a method is also a piece of code, but it's not “stranded” and self-standing, like a function is. A method is defined on an object and can be run against a specific object instance.
NOTE In much of the official Kotlin documentation, there is not a clear distinction drawn between a function and a method. However, I've chosen to draw this distinction because it's important in general object-oriented programming, and if you work or have familiarity with any other object-based language, you'll run across these terms. But you should realize that in “proper” Kotlin, all methods are functions.
That last sentence is important, so you may want to read it again. It's important because it means that a method can interact with the object instance. For example, a method might want to use the object instance's property values, like, say, a first name and last name. And with that in mind, back to your code!
Print an Object (and Do It with Shorthand)
You can run the println
function at any time, and you just pass it something to print. So you could say:
println("How are you?")
and you'd just get that output in your results window. You can also have it print the result from a method, like toString()
, which is what you did earlier. But there's another shortcut. If you pass in something to println()
that has a toString()
method, that method is automatically run. So you can actually trim this code:
println(brian.toString())
down to just this:
println(brian)
In the latter case, Kotlin sees an object passed to println()
and automatically runs brian.toString()
and passes the result on for printing. In either case, you'll get output that looks something like this:
Person@7c30a502
That's not very useful, is it? It's essentially an identifier for your specific instance of Person
that is useful to Kotlin internals and the JVM, but not much else. Let's fix that.
Override the toString() Method
One of the cool things about a class method is that you can write code and define what that method does. We haven't done that yet, but it's coming soon. In the meantime, though, what we have here is slightly different: a method that we didn't write code for, and that doesn't do what we want.
In this case, you can do something called overriding a method. This just means replacing the code of the method with your own code. That's exactly what we want to do here.
First, you need to tell Kotlin that you're overriding a method by using the override
keyword. Then you use another keyword, fun
, and then the name of the method to override, like this:
override fun toString()
NOTE Earlier, you learned the difference between a function and a method. And toString()
is definitely a method, in this case on Person
. So why are you using the fun
keyword? That looks an awful lot like “function,” and that's, in fact, what it stands for.
The official answer is that Kotlin essentially sees a method as a function attached to an object. And it was easier to not use a different keyword for a standalone function and an actual method.
But, if that bugs you, you're in good company. It bugs me, too! Still, for the purposes of Kotlin, you define both functions and methods with fun
.
But toString()
adds a new wrinkle: it returns a value. It returns a String
to print. And you need to tell Kotlin that this method returns something. You do that with a colon after the parentheses and then the return type, which in this case is a String
:
override fun toString(): String
Now you can write code for the method, between curly braces, like this:
class Person(firstName: String, lastName: String) { override fun toString(): String { return "$firstName $lastName" } }
This looks good, and you've probably already figured out that putting a dollar sign ( $
) before a variable name lets you access that variable. So this takes the firstName
and lastName
variables passed into the Person
constructor and prints them, right?
Well, not exactly. If you run this code, you'll actually get the errors shown in Figure 1.10.
FIGURE 1.10 Why doesn't this override of toString() work?
What gives here? Well, it turns out to be a little tricky.
All Data Is Not a Property Value
You have a constructor and it takes in two pieces of data: firstName
and lastName
. That's controlled by the constructor declaration:
class Person(firstName: String, lastName: String) {
But here's what is tricky: just accepting those values does not actually turn them into property values. That's why you get the error in Figure 1.10; your Person
object accepted a first and last name, but then promptly ignored them. They aren't available to be used in your toString()
overridden method.
You need to use the val
keyword in front of each piece of data to turn that data into property values. Here's the change you need to make:
class Person(val firstName: String, val lastName: String) {
Specifically, by using val
(or var
, which we'll talk about shortly), you've created variables, and assigned them to the Person
instance being created. That then allows those variables (or properties, to be even more precise) to be accessed, like in your toString()
method.
Make these changes (see Listing 1.5 to make sure you're caught up) and then compile and run your program.
LISTING 1.5: Converting data to actual properties
class Person(val firstName: String, val lastName: String) { override fun toString(): String { return "$firstName $lastName" } } fun main() { val brian = Person("Brian", "Truesby") println(brian) }
You should get a single line of output:
Brian Truesby
Obviously, the name will be different if you passed in different values for first and last name, but the result is the same, and it's a big deal. You've now:
Created a new object
Defined