Programming Kotlin Applications. Бретт Мак-Лахлин
a constructor for the object
Accepted two pieces of data in that constructor and stored them as properties associated with the object instance
Overridden a method and made it useful
Written a main function
Instantiated your custom object and passed in values
Used the object to print itself out, using your overridden method
Not too bad for getting started! There's just one more detail to work through before closing shop on your first foray into Kotlin.
INITIALIZE AN OBJECT AND CHANGE A VARIABLE
Suppose you want to play around a bit with your Person
class. Try this out: update your code to match Listing 1.6 (some of this may be confusing, but you can probably figure out most of what's going on).
LISTING 1.6: Creating a new property for a Person
class Person(val firstName: String, val lastName: String) { val fullName: String // Set the full name when creating an instance init { fullName = "$firstName $lastName" } override fun toString(): String { return fullName } } fun main() { // Create a new person val brian = Person("Brian", "Truesby") // Create another person val rose = Person("Rose", "Bushnell") println(brian) }
You'll see a number of new things here, but none are too surprising. First, a new variable is declared inside the Person
object: fullName
. This is something you've already done in your main
function. This time, though, because you're doing it inside the Person
object, it automatically becomes part of each Person
instance.
Another small change is the addition of a new Person
instance in main
; this time it's a variable named rose
.
Then, there's a new keyword: init
. That bears further discussion.
Initialize a Class with a Block
In most programming languages, Java included, a constructor takes in values (as it does in your Person
class) and perhaps does some basic logic. Kotlin does this a bit differently; it introduces the idea of an initializer block. It's this block—identified conveniently with the init
keyword—where you put code that should run every time an object is created.
This is a bit different than you may be used to: data comes in through a constructor, but it's separated from the initialization code, which is in the initializer block.
In this case, the initializer block uses the new fullName
variable and sets it using the first and last name properties passed in through the class constructor:
// Set the full name when creating an instance init { fullName = "$firstName $lastName" } Then this new variable is used in toString(): override fun toString(): String { return fullName }
WARNING As much new material as this chapter introduces, you may have just run across the most important thing that you may learn, in the long-term sense. By changing toString()
to use fullName
, rather than also using the firstName
and lastName
variables directly, you are implementing a principle called DRY: Don't Repeat Yourself.
In this case, you're not repeating the combination of first name and last name, which was done already in the initializer block. You assign that combination to a variable, and then forever more, you should use that variable instead of what it actually references. More on this later, but take note here: this is a big deal!
Kotlin Auto-Generates Getters and Setters
At this point, things are going well. Part of that is all you've added, but another big help is that Kotlin is doing a lot behind the scenes. It's running code for you automatically (like that initializer block) and letting you override methods.
It's doing something else, too: it's auto-generating some extra methods on your class. Because you made firstName
and lastName
property values (with that val
keyword), and you defined a fullName
property, Kotlin created getters and setters for all of those properties.
Terminology Update: Getters, Setters, Mutators, Accessors
A getter is a method that allows you to get a value. For instance, you can add this into your main
function, and it will not only work, but print out just the first name of the brian Person
instance:
// Create a new person val brian = Person("Brian", "Truesby") println(brian.firstName)
This works because you have a getter on Person
for firstName
. You can do the same with fullName
and lastName
, too. This getter is, more formally, an accessor. It provides access to a value, in this case a property of Person
. And it's “free” because Kotlin creates this accessor for you.
Kotlin also gives you a setter, or (again, more formally) a mutator. A mutator lets you mutate a value, which just means to change it. So you can add this into your program:
// Create a new person val brian = Person("Brian", "Truesby") println(brian.firstName) // Create another person val rose = Person("Rose", "Bushnell") rose.lastName = "Bushnell-Truesby"
Just as you can get data through an accessor, you can update data through mutators.
WARNING For the most part, I'll be calling getters accessors, and calling setters mutators. That's not as common as “getter” or “setter,” but as a good friend and editor of mine once told me, a setter is a hairy and somewhat fluffy dog; a mutator lets you update class data. The difference—and his colorful explanation—has stuck with me for 20 years.
Now, if you've gone ahead and compiled this code, you've run into yet another odd error, and that's the last thing to fix before moving on from this initial foray into objects.
Constants Can't Change (Sort of)
Here's the code causing the problem:
// Create another person val rose = Person("Rose", "Bushnell") rose.lastName = "Bushnell-Truesby"
If you try to run this code, you'll get an error like this:
Error: Kotlin: Val cannot be reassigned
One of the things that is fairly unique about Kotlin is its strong stance on variables. Specifically, Kotlin allows you to not just declare the type of a variable, but also whether that variable is a mutable variable, or a constant variable.
NOTE The terminology here is a bit confusing, so take your time. Just as with methods being declared with the fun
keyword, the idea of a constant variable takes a little getting used to.
When you declare a variable in Kotlin, you can use the keyword val
, as you've already done: