SitePoint
  • Blog
  • Forum
  • Library
  • Login
Join Premium
Programming Kotlin Applications
Close
    • Programming Kotlin® Applications
    • ABOUT THE AUTHOR
    • ABOUT THE TECHNICAL EDITOR
    • ACKNOWLEDGMENTS
    • INTRODUCTION
    • WHAT DOES THIS BOOK COVER?
    • WILL THIS BOOK TEACH ME TO PROGRAM MOBILE APPLICATIONS IN KOTLIN?
    • KOTLIN: A NEW PROGRAMMING LANGUAGE
    • WHAT IS KOTLIN?
    • KOTLIN IS OBJECT-ORIENTED
    • INTERLUDE: SET UP YOUR KOTLIN ENVIRONMENT
    • CREATING USEFUL OBJECTS
    • INITIALIZE AN OBJECT AND CHANGE A VARIABLE
    • UPGRADE YOUR KOTLIN CLASS GAME
    • KOTLIN HAS A LARGE NUMBER OF TYPES
    • OVERRIDING PROPERTY ACCESSORS AND MUTATORS
    • CLASSES CAN HAVE CUSTOM BEHAVIOR
    • TYPE SAFETY CHANGES EVERYTHING
    • WRITING CODE IS RARELY LINEAR
    • OBJECTS, CLASSES, AND KOTLIN
    • ALL CLASSES NEED AN EQUALS(X) METHOD
    • EVERY OBJECT INSTANCE NEEDS A UNIQUE HASHCODE()
    • SEARCHING (AND OTHER THINGS) DEPEND ON USEFUL AND FAST EQUALS(X) AND HASHCODE()
    • BASIC CLASS METHODS ARE REALLY IMPORTANT
    • GOOD CLASSES ARE NOT ALWAYS COMPLEX CLASSES
    • CLASSES CAN DEFINE DEFAULT VALUES FOR PROPERTIES
    • SECONDARY CONSTRUCTORS PROVIDE ADDITIONAL CONSTRUCTION OPTIONS
    • HANDLE DEPENDENT VALUES WITH CUSTOM MUTATORS
    • NEED SPECIFICS? CONSIDER A SUBCLASS
    • YOUR SUBCLASS SHOULD BE DIFFERENT THAN YOUR SUPERCLASS
    • LISTS ARE JUST A COLLECTION OF THINGS
    • LISTS (AND COLLECTIONS) CAN BE TYPED
    • LISTS ARE ORDERED AND CAN REPEAT
    • SETS: UNORDERED BUT UNIQUE
    • MAPS: WHEN A SINGLE VALUE ISN'T ENOUGH
    • FILTER A COLLECTION BY … ANYTHING
    • COLLECTIONS: FOR PRIMITIVE AND CUSTOM TYPES
    • GENERICS ALLOW DEFERRING OF A TYPE
    • GENERICS TRY TO INFER A TYPE WHEN POSSIBLE
    • COVARIANCE: A STUDY IN TYPES AND ASSIGNMENT
    • CONTRAVARIANCE: BUILDING CONSUMERS FROM GENERIC TYPES
    • UNSAFEVARIANCE: LEARNING THE RULES, THEN BREAKING THEM
    • TYPEPROJECTION LETS YOU DEAL WITH BASE CLASSES
    • CONTROL STRUCTURES ARE THE BREAD AND BUTTER OF PROGRAMMING
    • IF AND ELSE: THE GREAT DECISION POINT
    • WHEN IS KOTLIN'S VERSION OF SWITCH
    • FOR IS FOR LOOPING
    • USE WHILE TO EXECUTE UNTIL A CONDITION IS FALSE
    • DO … WHILE ALWAYS RUNS ONCE
    • GET OUT OF A LOOP IMMEDIATELY WITH BREAK
    • GO TO THE NEXT ITERATION IMMEDIATELY WITH CONTINUE
    • RETURN RETURNS
    • CLASSES IN THE REAL WORLD ARE VARIED BUT WELL EXPLORED
    • A DATA CLASS TAKES THE WORK OUT OF A CLASS FOCUSED ON DATA
    • DESTRUCTURING DATA THROUGH DECLARATIONS
    • YOU CAN “COPY” AN OBJECT OR MAKE A COPY OF AN OBJECT
    • DATA CLASSES REQUIRE SEVERAL THINGS FROM YOU
    • DATA CLASSES ADD SPECIAL BEHAVIOR TO GENERATED CODE
    • DATA CLASSES ARE BEST LEFT ALONE
    • STRINGS ARE TERRIBLE AS STATIC TYPE REPRESENTATIONS
    • COMPANION OBJECTS ARE SINGLE INSTANCE
    • ENUMS DEFINE CONSTANTS AND PROVIDE TYPE SAFETY
    • SEALED CLASSES ARE TYPE-SAFE CLASS HIERARCHIES
    • REVISITING THE SYNTAX OF A FUNCTION
    • FUNCTIONS FOLLOW FLEXIBLE RULES
    • FUNCTIONS IN KOTLIN HAVE SCOPE
    • FUNCTION LITERALS: LAMBDAS AND ANONYMOUS FUNCTIONS
    • LOTS OF FUNCTIONS, LOTS OF ROOM FOR PROBLEMS
    • SCOPE FUNCTIONS PROVIDE CONTEXT TO CODE
    • USE LET TO PROVIDE IMMEDIATE ACCESS TO AN INSTANCE
    • WITH IS A SCOPE FUNCTION FOR PROCESSING AN INSTANCE
    • RUN IS A CODE RUNNER AND SCOPE FUNCTION
    • APPLY HAS A CONTEXT OBJECT BUT NO RETURN VALUE
    • ALSO GIVES YOU AN INSTANCE … BUT OPERATES ON THE INSTANCE FIRST
    • SCOPE FUNCTIONS SUMMARY
    • ABSTRACT CLASSES REQUIRE A LATER IMPLEMENTATION
    • INTERFACES DEFINE BEHAVIOR BUT HAVE NO BODY
    • DELEGATION OFFERS ANOTHER OPTION FOR EXTENDING BEHAVIOR
    • INHERITANCE REQUIRES FORETHOUGHT AND AFTERTHOUGHT
    • PROGRAMMING KOTLIN FOR ANDROID
    • KOTLIN AND JAVA ARE GREAT COMPANIONS
    • WHEN KOTLIN QUESTIONS STILL EXIST
    • NOW WHAT?
    • WILEY END USER LICENSE AGREEMENT

Objects All the Way Down

WHAT'S IN THIS CHAPTER?

  • A look at Kotlin syntax
  • A brief history of Kotlin
  • How Kotlin is like Java—and how it isn't
  • Getting set up to code and run Kotlin
  • Your first Kotlin program
  • Why objects are cool (and why that matters)

KOTLIN: A NEW PROGRAMMING LANGUAGE

Kotlin, when you boil it all down, is just another programming language. If you're programming and writing code already, you'll pick up Kotlin quickly, because it has a lot in common with what you're already doing. That's even more the case if you're programming in an object-oriented language, and if you're coding in Java, well, Kotlin is going to feel very familiar, although different in some very nice ways.

If you're new to Kotlin, though, it's a great first language. It's very clear, it doesn't have lots of odd idioms (like, for example, Ruby or, god help us all, LISP), and it's well organized. You'll pick it up fast and find yourself comfortable quite quickly.

In fact, Kotlin is so straightforward that we're going to put aside a lot of explanation and history for now, and instead jump right into looking at some basic Kotlin code (check out Listing 1.1).

LISTING 1.1: A simple Kotlin program using classes and lists

data class User(val firstName: String, val lastName: String) fun main() {  val brian = User("Brian", "Truesby")  val rose = User("Rose", "Bushnell")   val attendees: MutableList<User> = mutableListOf(brian, rose)   attendees.forEach {    user -> println("$user is attending!")  }}

Take a minute or two to read through this code. Even if you've never looked at a line of Kotlin before, you can probably get a pretty good idea of what's going on. First, it defines a User class (and in fact, a special kind of class, a data class; more on that later). Then it defines a main function, which is pretty standard fare. Next up are two variables (or vals), each getting an instance of the User class defined earlier. Then a list is created called attendees and filled with the two users just created. Last up is a loop through the list, doing some printing for each.

If you ran this code, you'd get this rather unimpressive output:

User(firstName=Brian, lastName=Truesby) is attending!User(firstName=Rose, lastName=Bushnell) is attending!

Obviously, parts of this probably look odd to you, whether you're brand new to writing code or an experienced Java pro. On top of that, it's likely you have no idea how to actually compile or run this code. That's OK, too. We'll get to all of that.

Note

It bears repeating: You really don't need to understand the code in Listing 1.1. This book assumes you've programmed at least a little bit—and it's true that you'll likely understand Kotlin a bit faster if you have a Java background—but you will pick up everything you see in Listing 1.1 (and quite a bit more) just by continuing to read and working through the code samples. Just keep going, and you'll be programming in Kotlin in no time.

For now, though, here's the point: Kotlin is really approachable, clean to read, and actually a pretty fun language to use. With that in mind, let's get some basics out of the way so you can get to writing code, not just looking at it.

WHAT IS KOTLIN?

Kotlin is an open-source programming language. It is, most notably, statically typed and object-oriented. Statically typed means that variables have types when you write your code and compile it, and those types are fixed. That also implies that Kotlin must be compiled, which is also true. Object-oriented means it has classes and inheritance, making it a familiar language for Java and C++ developers.

Kotlin was in fact created by a group of developers that worked on the JetBrains IDE, and it feels very much like a natural evolution of Java. It's been around in early form since 2011, but was officially released in 2016. That means it's new, which is good, but also means it's new, which at times can be bad. Kotlin is modern, can run inside a Java Virtual Machine (JVM), and can even be compiled to JavaScript—a cool feature we'll look at a little later.

It's also really important to note that Kotlin is a fantastic language for writing Android apps. In fact, many of its enhancements to Java reflect an Android usage. That said, even if you never intended to write a mobile app, you'll find Kotlin a welcome addition to your arsenal, and well suited for server-side programming.

What Does Kotlin Add to Java?

That's a good question that has a long answer. In fact, we'll spend most of this book answering that in various forms. But, for most, Kotlin adds or changes a few key features when compared to Java:

Note

If you're new to Kotlin or not coming from a Java background, feel free to skip right on to the next section.

  • Kotlin ditches NullPointerException (and nullable variables altogether) in almost all situations.
  • Kotlin supports extending functions without having to entirely override a parent class.
  • Kotlin doesn't support checked exceptions (you may not find this to be an advancement, so fair warning).
  • Kotlin adds components of functional programming, such as extensive lambda support and lazy evaluation.
  • Kotlin defines data classes that let you skip writing basic getters and setters.

There's certainly a lot more to this list, but you can quickly see that Kotlin isn't just a slightly different version of Java. It seeks to be different and better, and in many ways, it very much is exactly that.

KOTLIN IS OBJECT-ORIENTED

At this point, most books and tutorials would have you put together a simple “Hello, World” program. That's all well and good, but the assumption here is that you want to get moving, and get moving quickly. For that reason, the logical place to begin with Kotlin is by creating an object.

An object is simple a programmatic representation of a thing. In the best case, that thing is a real-world object, like a car or a person or a product. For example, you could create an object to model a person like this:

class Person {   /* This class literally does nothing! */ }

That's it. You can now create a new variable of type Person like this:

fun main() {    val jennifer = Person()}

If you put all this together into a single code listing, you'll have Listing 1.2.

LISTING 1.2: A very useless object in Kotlin (and a main function to use it)

class Person {   /* This class literally does nothing! */ } fun main() {    val jennifer = Person()}

Now, this is pretty lame code, honestly. It doesn't do anything, but it is object-oriented. Before we can improve it, though, you need to be able to run this good-for-almost-nothing code yourself.

INTERLUDE: SET UP YOUR KOTLIN ENVIRONMENT

Getting a Kotlin program to run is relatively easy, and if you're an old Java hand, it's actually really easy. You'll need to install a Java Virtual Machine and then a Java Development Kit (JDK). Then you'll want one of the numerous IDEs that support Kotlin. Let's take a blazing-fast gallop through that process.

Install Kotlin (and an IDE)

One of the easiest IDEs to use with Kotlin is IntelliJ IDEA, and starting with version 15, IntelliJ comes bundled with Kotlin. Plus, since IntelliJ is actually from JetBrains, you're getting an IDE built by the same folks who came up with Kotlin itself.

Install IntelliJ

You can download IntelliJ from www.jetbrains.com/idea/download. This page (shown in Figure 1.1) will then redirect you to the appropriate platform (Mac OS X for most of this book's examples). Download the free Community version to get started without any cost. Once the (rather large) download completes, install it (see Figure 1.2), and you'll get a Java Runtime Environment (JRE) and the JDK as part of installation.

Snapshot of Download IntelliJ from the JetBrains download page.

FIGURE 1.1 Download IntelliJ from the JetBrains download page.

Note

IntelliJ is not the only IDE that works with Kotlin, and the list is actually growing pretty quickly. Other notable options are Android Studio (developer.android.com/studio/preview/index.html) and Eclipse (www.eclipse.org/downloads). Eclipse in particular is immensely popular, but IntelliJ is still a great choice as it shares the JetBeans heritage with Kotlin.

Snapshot of IntelliJ comes prepackaged with a system-specific installation process.

FIGURE 1.2 IntelliJ comes prepackaged with a system-specific installation process.

Note

The “installation process” for IntelliJ on Mac OS X is pretty simple: just drag the package (presented as an icon) into your Applications folder. You'll then need to go to that folder and launch IntelliJ or drag the icon into your Dock, which is what I've done.

For Windows, you download the executable and run it. You can then create a shortcut on your desktop if you like.

In both cases, you can use the JetBrains Toolbox (which comes with the JetBrains Kotlin package) to keep your installation current and add updates when they're available.

You'll be given a pretty large number of options to get your IDE set up. For IntelliJ, you'll pick a UI theme (either is fine), a Launcher Script (I'd suggest you accept the default and let it create the script), the default plugins, and a set of featured plugins. You can click through these quickly, and then your IDE will restart. You'll see a welcome screen similar to Figure 1.3, and you should select Create New Project.

Warning

You may need advanced permissions to install the Launcher Script that IntelliJ creates if you accepted the default location on Mac OS X.

Be sure you select the Kotlin/JVM option when creating the project, as shown in Figure 1.4.

Snapshot of creating a project from scratch or importing one from a code repository, like GitHub.

FIGURE 1.3 You'll generally be either creating a project from scratch or importing one from a code repository, like GitHub.

Snapshot of IntelliJ makes getting going in Kotlin simple and prompts you on creating a new project to include Kotlin libraries.

FIGURE 1.4 IntelliJ makes getting going in Kotlin simple and prompts you on creating a new project to include Kotlin libraries.

Create Your Kotlin Program

Once your project is up and running, create a new Kotlin file. Find the src/ folder in the left navigation pane, right-click that folder, and select New ➢ Kotlin File/Class (see Figure 1.5). You can enter the code from Listing 1.2, and it should look nice and pretty, as shown in Figure 1.6 (thanks IntelliJ!).

Note

Your IDE may not be configured exactly like mine. If you don't see the src/ folder, you may need to click Project on the left side of your IDE to display the various folders, and possibly click again on the name of the project.

Snapshot of Kotlin code should go in the src/ folder.

FIGURE 1.5 Kotlin code should go in the src/ folder.

Note

From this point forward, code will typically not be shown in an IDE. That way, you can use the IDE of your choice (or the command line), because you should get the same results across IDEs.

Snapshot of IntelliJ automatically formats code and adds sensible syntax highlighting.

FIGURE 1.6 IntelliJ automatically formats code and adds sensible syntax highlighting.

Compile and Run Your Kotlin Program

All that's left now is to compile and run the program yourself. This is easy, because IntelliJ gives you a convenient little green arrow to click when you have a Kotlin file with a main() function defined. Just hover over the arrow and click (see Figure 1.7). You can then select Run and your filename (I named mine “UselessPerson”). Your program will be compiled and run, with the output shown in a new pane at the bottom of the IDE (see Figure 1.8).

Snapshot of clicking the green Run button and select the first option to build and run your code.

FIGURE 1.7 You can click the green Run button and select the first option to build and run your code.

Snapshot of the empty output of your program displaying in its own window.

FIGURE 1.8 The empty output of your program (which will soon be non-empty) displays in its own window.

In this case, you shouldn't get any errors, but there's not any output either. We'll fix that shortly.

Fix Any Errors as They Appear

One last note before getting back to improving that useless Person class. IntelliJ and all other IDEs are great at giving you visual indicators when there is a problem with your code. For example, Figure 1.9 shows IntelliJ once it's tried to compile the same program with an error. In this case, the open and close parentheses are missing from line 8. You'll see an orange indicator in the code editor and an error indicating line 8 (and column 20) in the output window.

You can then easily fix the error and rebuild.

Install Kotlin (and Use the Command Line)

For power users, there's a tendency to want to use the command line for nearly everything. Kotlin is no exception. Because it's “mostly Java” in the sense that it runs using a JVM and JDK, you can get pretty far without a lot of work.

Command-Line Kotlin on Windows

For Windows users, you'll first need a JDK. You can download one from the Oracle Java download page at www.oracle.com/technetwork/java/javase/downloads. That download has version-specific instructions that are easy to follow.

Once you have Java, you need to get the latest Kotlin release from GitHub. You can find that at github.com/JetBrains/kotlin/releases/latest (that link will redirect you to the latest release). Download the release and follow the instructions and you'll be good to go.

Snapshot of Good IDEs helping you quickly find and fix errors.

FIGURE 1.9 Good IDEs help you quickly find and fix errors.

Note

These instructions are intentionally a bit sparse. If you're using the command line already, you probably don't need a lot of hand holding. For almost everyone else, though, using an IDE really is the best approach. As a bonus, you can also use IntelliJ as a proxy for the compiler, so you may just want to save the time it would take you to mess with the command line and put it into coding Kotlin!

Command-Line Kotlin on Mac OS X

The easiest path to getting Kotlin working on Mac OS X is to use one of the package managers popular on Macs: either Homebrew (brew.sh) or MacPorts (www.macports.org). Both of these make getting Kotlin up and running trivial.

For MacPorts, just run the following command:

brett $ sudo port install kotlin

This requires elevated permissions, but after it runs, you'll be all set.

For Homebrew, first do an update:

brett $ brew update

Next up, install Kotlin:

brett $ brew install kotlin

Command-Line Kotlin on UNIX-Based Systems

If you're not on Mac OS X but still have a Unix flavor of operating system, you can use SDKMAN! (sdkman.io) for installing Kotlin.

Note

To be accurate, Mac OS X is a Unix-based operating system, so you can use the SDKMAN! instructions for Macs instead of Homebrew or MacPorts.

First, get SDKMAN!:

brett $ curl -s https://get.sdkman.io | bash

When you're finished, you'll need to open a new terminal or shell window or source the modified file as indicated at the end of the installation process.

Now, install Kotlin:

brett $ sdk install kotlin

Verify Your Command-Line Installation

However you've chosen to install Kotlin, when you're finished, you should be able to validate your installation with this command:

brett $ kotlinc

Warning

At this point, you may get prompted to install a Java runtime. This should fire up your system to handle this, and you can accept the prompts without a lot of worry. Find the JDK or JRE for your system, download it, and run it. Then come back and try out kotlinc again.

If you have your system appropriately configured with Java, you should get back something like this:

brett $ kotlinc Java HotSpot(TM) 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release. Welcome to Kotlin version 1.3.61 (JRE 13.0.2+8) Type :help for help, :quit for quit >>>

This is the Kotlin REPL (Read-Eval-Print Loop), a tool for quickly evaluating Kotlin statements. We'll look at this in more detail later, but for now, exit the REPL by typing :quit.

You can also verify your version of Kotlin with the following command:

brett $ kotlin -version Kotlin version 1.3.61-release-180 (JRE 13.0.2+8)

At this point, you're ready to roll!

CREATING USEFUL OBJECTS

With a working Kotlin environment, it's time to go make that Person class from Listing 1.2 something less vacuous. As mentioned earlier, objects should model real-world objects. Specifically, if an object represents a “thing” in the world (or in formal circles you'll sometimes hear that objects are “nouns”), then it should have the properties or attributes of that thing, too.

A person's most fundamental property is their name, specifically a first name and last name (or surname, if you like). These can be represented as properties of the object. Additionally, these are required properties; you really don't want to create a person without a first and last name.

For required properties, it's best to require those properties when creating a new instance of an object. An instance is just a specific version of the object; so you might have multiple instances of the Person class, each one representing a different actual person.

Warning

As you may already be figuring out, there's a lot of technical vocabulary associated with classes. Unfortunately, it will get worse before it gets better: instances and instantiation and constructors and more. Don't get too worried about catching everything right away; just keep going, and you'll find that the vocabulary becomes second nature faster than you think. In Chapter 3, you'll dive in deeper, and in fact, you'll keep revisiting and growing your class knowledge throughout the entire book.

Pass In Values to an Object Using Its Constructor

An object in Kotlin can have one or more constructors. A constructor does just what it sounds like: it constructs the object. More specifically, it's a special method that runs when an object is created. It can also take in property values—like that required first and last name.

You put the constructor right after the class definition, like this:

class Person constructor(firstName: String, lastName: String) {

In this case, the constructor takes in two properties: firstName and lastName, both String types. Listing 1.3 shows the entire program in context, along with creating the Person instance by passing in the values for firstName and lastName.

Warning

You'll sometimes hear properties or property values called parameters. That's not wrong; a parameter is usually something passed to something else; in this case, something (a first name and last name) passed to something else (a constructor). But once they're assigned to the object instance, they're no longer parameters. At that point, they are properties (or more accurately, property values) of the object. So it's just easier to call that a property value from the start.

See? The terminology is confusing. Again, though, it will come with time. Just keep going.

LISTING 1.3: A less useless object in Kotlin and its constructor

class Person constructor(firstName: String, lastName: String) { /* This class still doesn't do much! */ } fun main() {    val brian = Person("Brian", "Truesby")}

Now the class takes in a few useful properties. But, as most developers know, there's a tendency to condense things in code. There's a general favoring of typing less, rather than typing more. (Note that this rarely applies to book authors!) So Listing 1.3 can be condensed; you can just drop the word constructor and things work the same way. Listing 1.4 shows this minor condensation (and arguable improvement).

LISTING 1.4: Cutting out the constructor keyword

class Person(firstName: String, lastName: String) {     /* This class still doesn't do much! */ } fun main() {    val brian = Person("Brian", "Truesby")}

Print an Object with toString()

This is definitely getting a little better. But the output is still empty, and the class is still basically useless. However, Kotlin gives you some things for free: most notably for now, every class automatically gets a toString() method. You can run this method by creating an instance of the class (which you've already done) and then calling that method, like this:

val brian = Person("Brian", "Truesby") println(brian.toString())

Make this change to your main function. Create a new Person (give it any name you want), and then print the object instance using println and passing into println the result of toString().

Note

You may be wondering where in the world that toString() method came from. (If not, that's OK, too.) It does seem to sort of magically appear. But it's not magical it all. It's actually inherited. Inheritance is closely related to objects, and something we'll talk about in a lot more detail in both Chapter 3 and Chapter 5.

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.

Snapshot of why doesn't this override of toString() work?

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 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 personval 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 personval brian = Person("Brian", "Truesby")println(brian.firstName) // Create another personval 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 personval 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:

val brian = Person("Brian", "Truesby")

But you can also use the keyword var, something you haven't done yet. That would look like this:

var brian = Person("Brian", "Truesby")

First, in both cases, you end up with a variable; val does not stand for value, for example, but is simply another way to declare a variable, alongside var. When you use val, you are creating a constant variable. In Kotlin, a constant variable can be assigned a value once, and only once. That variable is then constant and can never be changed.

You created the lastName variable in Person with this line:

class Person(val firstName: String, val lastName: String) {

That defines lastName (and firstName) as a constant variable. Once it's passed in and assigned when the Person instance is created, it can't be changed. That makes this statement illegal:

rose.lastName = "Bushnell-Truesby"

To clear up the odd error from earlier, what you need instead is for lastName to be a mutable variable; you need it to be changeable after initial assignment.

Note

Not to beat a hairy and somewhat fluffy dog to death, but here is another reason to use mutator over setter; a mutator allows you to mutate a mutable variable. This aligns the terminology much more cleanly than using “setter.”

So change your Person constructor to use var instead of val. This indicates that firstName and lastName can be changed:

class Person(var firstName: String, var lastName: String) {

Now you should be able to compile the program again, without error. In fact, once you've done that, make a few other tweaks. You want to end up with your code looking like Listing 1.7.

LISTING 1.7 Using mutable variables

class Person(var firstName: String, var lastName: String) {    var 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")    println(brian) // Create another person val rose = Person("Rose", "Bushnell")    println(rose) // Change Rose's last name rose.lastName = "Bushnell-Truesby"    println(rose)}

Here, fullName has been made mutable, and there's a little more printing in main. It should compile and run without error now.

But wait! Did you see your output? There's a problem! Here's what you probably get when you run your code:

Brian TruesbyRose BushnellRose Bushnell

Despite what Meat Loaf had to say about two out of three not being bad, this is not great. Why is Rose's name in the last instance not printing with her new last name?

Well, to solve that, it's going to take another chapter, and looking a lot more closely at how Kotlin handles data, types, and more of that automatically running code.

End of PreviewSign Up to unlock the rest of this title.

Community Questions