Chapter 4. classes and objects: A Bit of Class

image

It’s time we looked beyond Kotlin’s basic types.

Sooner or later, you’re going to want to use something more than Kotlin’s basic types. And that’s where classes come in. Classes are templates that allow you to create your own types of objects, and define their properties and functions. Here, you’ll learn how to design and define classes, and how to use them to create new types of objects. You’ll meet constructors, initializer blocks, getters and setters, and you’ll discover how they can be used to protect your properties. Finally, you’ll learn how data hiding is built into all Kotlin code, saving you time, effort and a multitude of keystrokes.

Object types are defined using classes

So far, you’ve learned how to create and use variables from Kotlin’s basic types, such as numbers, Strings and arrays. You know, for example, that when you write the code:

var x = 6

this creates an Int object with a value of 6, and a reference to the object is assigned to a new variable named x:

image

Behind the scenes, these types are defined using classes. A class is a template that defines what properties and functions are associated with objects of that type. When you create an Int object, for example, the compiler checks the Int class and sees that it requires an integer value, and has functions such as toLong and toString.

You can define your own classes

If you want your application to deal with types of objects that Kotlin doesn’t have, you can define your own types by writing new classes. If you’re building an application that records information about dogs, for example, you might want to define a Dog class so that you can create your own Dog objects, and record the name, weight and breed of each dog:

image

So how do you go about defining a class?

How to design your own classes

When you want to define your own class, you need to think about the objects that will be created from that class. You need to consider:

  • * The things each object knows about itself.

  • * The things each object can do.

The things an object knows about itself are its properties. They represent an object’s state (the data), and each object of that type can have unique values. A Dog class, for example, might have name, weight and breed properties. A Song class might have title and artist properties.

The things an object knows about itself are its properties.

The things an object can do are its functions. They determine an object’s behavior, and may use the object’s properties. The Dog class, for example, might have a bark function, and the Song class might have a play function.

The things an object can do are its functions.

Here are some examples of classes with their properties and functions:

image

When you know what properties and functions your class should have, you’re ready to write the code to create it. We’ll look at this next.

Let’s define a Dog class

We’re going to create a Dog class that we can use to create Dog objects. Each Dog will have a name, weight and breed, so we’ll use these for the class properties. We’ll also define a bark function so that the size of the Dog’s bark depends on its weight.

Here’s what our Dog class code looks like:

image

defines the name of the class (Dog), and the properties that the Dog class has. We’ll take a closer look at what’s going on behind the scenes a few pages ahead, but for now, all you need to know is that the above code defines the name, weight and breed properties—and when the Dog object is created, values are assigned to these properties.

A function that’s defined inside a class is called a member function. It’s sometimes called a method.

You define any class functions in the class body (inside the curly braces {}). We’re defining a bark function, so the code looks like this:

image

Now that you’ve seen the code for the Dog class, let’s look at how you use it to create a Dog object.

How to create a Dog object

You can think of a class as a template for an object, as it tells the compiler how to make objects of that particular type. It tells the compiler what properties each object should have, and each object made from that class can have its own values. Each Dog object, for example, would have name, weight and breed properties, with each Dog having its own values.

image

We’re going to use the Dog class to create a Dog object, and assign it to a new variable named myDog. Here’s the code:

image

When the code runs, it creates a new Dog object, and the arguments are used to assign values to the Dog’s properties. In our case, for example, we’re creating a new Dog object where the name property is “Fido”, the weight property is 70 pounds, and the breed property is “Mixed”:

image

Now that you’ve seen how to create a new Dog object, let’s look at how you access its properties and functions.

How to access properties and functions

Once you’ve created an object, you can access its properties using the dot operator (.). If you wanted to print a Dog’s name, for example, you would use code like this:

image

You can also update any properties that you have defined using the var keyword. As an example, here’s how you would update the Dog’s weight property to 75 pounds:

image

Note that the compiler won’t let you update any properties that you’ve defined using the val keyword. If you try to do so, you’ll get a compiler error.

You can also use the dot operator to call an object’s functions. If you wanted to call the Dog’s bark function, for example, you would use the following code:

image

What if the Dog is in a Dog array?

You can also add any objects you create to an array. If you wanted to create an array of Dogs, for example, you would use code like this:

image

This defines a variable named dogs, and as it’s an array that you’re populating with Dog objects, the compiler makes its type array<Dog>. Two Dog objects are then added to the array.

You can still access the properties and functions of each Dog object in the array. As an example, suppose you wanted to update the second Dog’s weight and make it bark. To do this, you would get a reference to the second item in the dogs array using dogs[1], and then use the dot operator to access the Dog’s weight property and bark function:

image

This is like saying “get the second object from the dogs array, change its weight to 15 pounds, and make it bark.”

Create a Songs application

Before we go any further into how classes work, we’re going to give you some more class practice by creating a new Songs project. We’ll add a Song class to the project, and create and use some Song objects.

Create a new Kotlin project that targets the JVM, and name the project “Songs”. Then create a new Kotlin file named Songs.kt by highlighting the src folder, clicking on the File menu and choosing New → Kotlin File/Class. When prompted, name the file “Songs”, and choose File from the Kind option.

image

Next, add the following code to Songs.kt:

image

Test drive

image

When we run the code, the following text gets printed in the IDE’s output window:

Playing the song Going Underground by The Jam
Stopped playing Going Underground
Playing the song Make Me Smile by Steve Harley

Now that you’ve seen how to define a class and use it to create objects, let’s dive into the mysterious world of object creation.

The miracle of object creation

When you declare and assign an object, there are three main steps:

  1. Declare a variable.

    var myDog = Dog("Fido", 70, "Mixed")
    image
  2. Create an object.

    var myDog = Dog("Fido", 70, "Mixed")
    image
  3. Link the object to the variable by assigning a reference.

    var myDog = Dog("Fido", 70, "Mixed")
    image

The big miracle happens at step two—when the object is created. There’s a lot going on behind the scenes, so let’s take a closer look.

How objects are created

When we define an object using code like:

image

it looks like we’re calling a function named Dog. But even though it looks and feels a lot like a function, it’s not. Instead, we’re calling the Dog constructor.

A constructor contains the code that’s needed to initialize an object. It runs before the object can be assigned to a reference, which means that you get a chance to step in, and do things to make the object ready for use. Most people use constructors to define an object’s properties and assign values to them.

Each time you create a new object, the constructor for that object’s class is invoked. So when you run the code:

var myDog = Dog("Fido", 70, "Mixed")

the Dog class constructor gets called.

A constructor runs when you instantiate an object. It’s used to define properties and initialize them.

What the Dog constructor looks like

When we created our Dog class, we included a constructor; it’s the parentheses and the code in between in the class header:

image

The Dog constructor defines three properties—name, weight and breed. Each Dog has these properties, and when the Dog gets created, the constructor assigns a value to each property. This initializes the state of each Dog, and ensures that it’s set up correctly.

Let’s take a look at what happens behind the scenes when the Dog constructor gets called.

Behind the scenes: calling the Dog constructor

Let’s go through what happens when we run the code:

var myDog = Dog("Fido", 70, "Mixed")
  1. The system creates an object for each argument that’s passed to the Dog constructor.

    It creates a String with a value of “Fido”, an Int with a value of 70, and a String with a value of “Mixed”.

    image
  2. The system allocates the space for a new Dog object, and the Dog constructor gets called.

    image
  3. The Dog constructor defines three properties: name, weight and breed.

    Behine the scenes, each property is a variable. A variable of the appropriate type is created for each property, as defined in the constructor.

    class Dog(val name: String,
              var weight: Int,
              val breed: String) {
    }
    image
  4. Each of the Dog’s property variables is assigned a reference to the appropriate value object.

    The name property, for example, is assigned a reference to the “Fido” String object, and so on.

    image
  5. Finally, a reference to the Dog object is assigned to a new Dog variable named myDog.

    image
image

That’s right—a property is a variable that’s local to the object.

This means that everything you’ve already learned about variables applies to properties. If you define a property using the val keyword, for example, this means that you can’t assign a new value to it. You can, however, update any properties that have been defined using var.

In our example, we’re using val to define the name and breed properties, and var to define the weight:

class Dog(val name: String, var weight: Int, val breed: String) {
    ...
}

This means that we can only update the Dog’s weight property, and not the Dog’s name or breed.

An object is sometimes known as an instance of a particular class, so its properties are sometimes called instance variables.

Code Magnets

image

Somebody used fridge magnets to write a noisy new DrumKit class, and a main function that prints the following output:

ding ding ba-da-bing!
bang bang bang!
ding ding ba-da-bing!

Unfortunately, the magnets have got scrambled. Can you piece the code back together again?

image

Code Magnets Solution

image

Somebody used fridge magnets to write a noisy new DrumKit class, and a main function that prints the following output:

ding ding ba-da-bing!
bang bang bang!
ding ding ba-da-bing!

Unfortunately, the magnets have got scrambled. Can you piece the code back together again?

image

Going deeper into properties

So far you’ve seen how to define a property by including it in the class constructor, and how doing so assigns a value to that property when the constructor is called. But what if you need to do something a little different? What if you want to validate a value before assigning it to a property? Or what if you want to initialize a property with a generic default value so that you don’t need to add it to the class constructor?

To find out how you can do this kind of thing, we need to take a closer look at constructor code.

Behind the scenes of the Dog constructor

As you already know, our current Dog constructor code defines three properties for the name, weight and breed of each Dog object, and assigns a value to each one when the Dog constructor is called:

class Dog(val name: String, var weight: Int, val breed: String) {
    ...
}

You can do this so concisely because the constructor code uses a shortcut for performing this kind of task. When the Kotlin language was developed, the brains behind it felt that defining and initializing properties was such a common action that it was worth making the syntax to do it very concise and simple.

If you were to perform the same action without using the shortcut, here’s what the code would look like:

image

Here, the three constructor parameters—name_param, weight_param and breed_param—have no val and var prefixes, which means that they no longer define properties. They are plain old parameters, just like the ones you see in function definitions. The name, weight and breed properties are instead defined in the main body of the class. Each one is assigned the value of the associated constructor parameter.

So how does this allow us to do more with our properties?

Flexible property initialization

Defining properties in the main body of the class gives you a lot more flexibility than adding them to the constructor, as it means that you no longer have to initialize each one with a parameter value.

Suppose that you wanted to assign a default value to a property without including it in the constructor. You might, for example, want to add an activities property to the Dog class, and initialize it with a default array containing a value of “Walks”. Here’s the code to do this:

image
image

Alternatively, you might want to tweak the value of a constructor parameter before assigning it to a property. You might, for example, want to record an uppercase String for the breed property instead of the value that’s passed to the constructor. To do this, you would use the toUpperCase function to create an uppercase version of the String, which you would then assign to the breed property like this:

image

Initializing a property in this way works well if you want to assign a simple value or expression to it. But what if you need to do something more complex?

How to use initializer blocks

If you need to initialize a property to something more complex than a simple expression, or if there’s extra code you want to run when each object is created, you can use one or more initializer blocks. Initializer blocks are executed when the object is initialized, immediately after the constructor is called, and they’re prefixed with the init keyword. Here’s an example of an initializer block that prints a message whenever a Dog object is initialized:

image

Your class can have multiple initializer blocks. Each one runs in the order in which it appears in the class body, interleaved with any property initializers. Here’s an example of some code with multiple initializer blocks:

image

As you’ve seen, there are various ways in which you can initialize your variables. But is it necessary?

You MUST initialize your properties

Back in Chapter 2, you learned that every variable you declare in a function must be initialized before it can be used. This also applies to any properties you define in a class: you must initialize properties before you try to use them. This is so important that if you declare a property without initializing it in either the property declaration or the initializer block, the compiler will get very upset and refuse to compile your code. The following code, for example, won’t compile because we’ve added a new property named temperament which hasn’t been initialized:

image

Nearly all of the time, you’ll be able to assign default values to your properties. In the above example, for instance, your code will compile if you initialize the temperament property to "":

image

How do you validate property values?

Earlier in the chapter, you learned how to directly get or set a property’s value using the dot operator. You already know, for example, that you can print the Dog’s name using:

println(myDog.name)

and that you can set its weight to 75 pounds using:

myDog.weight = 75

But in the hands of the wrong person, allowing direct access to all our properties in this way can be quite a dangerous weapon. Because what’s to prevent someone writing the following code:

image

A Dog with negative weight would be a Bad Thing.

To stop this kind of thing from happening, we need some way of validating a value before it’s assigned to a property.

The solution: custom getters and setters

If you want to tweak a property’s return value, or validate a value before it gets assigned to a property, you can write your own getters and setters.

Getters and setters let you, well, get and set property values. A getter’s sole purpose in life is to send back a return value, the value of whatever it is that particular getter is supposed to be getting. And a setter lives and breathes for the chance to take an argument value, and use it to set the value of a property.

Note

If you’re into being all formal about it, you might prefer to call them accessors and mutators instead.

Writing custom getters and setters lets you protect your property values, and they give you more control over what values are returned or assigned. We’ll show you how they work by adding two new things to our Dog class:

  • * A custom getter to return the Dog’s weight in kilograms.

  • * A custom setter to validate a proposed value for the Dog’s weight before we assign it.

Let’s start by creating a custom getter to return the Dog’s weight in kilograms.

How to write a custom getter

In order to add a custom getter that will allow us to return the Dog’s weight in kilograms, we’re going to do two things: add a new property to the Dog class named weightInKgs, and write a custom getter for it which will return the appropriate value. Here’s the code to do both these things:

image

The line:

get() = weight / 2.2

defines the getter. It’s a no parameter function named get that you add to the property. You add it to the property by writing it immediately below the property declaration. Its return type must match that of the property whose value you want to return or the code won’t compile. In the above example, the weightInKgs property is a Double, so the property’s getter must also return a Double.

Note

Technically, getters and setters are optional parts of the property declaration.

Each time you ask for the value of a property using code like:

myDog.weightInKgs

the property’s getter gets called. The above code, for example, calls the getter for the weightInKgs property. The getter uses the Dog’s weight property to calculate the Dog’s weight in kilograms, and returns the result.

Note that in this example, we didn’t need to initialize the weightInKgs property because its value is derived in the getter. Each time the property’s value is required, the getter is called, which figures out the value that should be returned.

Now that you know how to add a custom getter, let’s look at how you add a custom setter by adding one to the weight property.

How to write a custom setter

We’re going to add a custom setter to the weight property so that the weight can only be updated to a value greater than 0. To do this, we need to move the weight property definition from the constructor to the class body, and then add the setter to the property. Here’s the code to do that:

image

The following code defines the setter:

set(value) {
    if (value > 0) field = value
}

A setter is a function named set that’s added to the property by writing it beneath the property declaration. A setter has one parameter—usually named value—which is the proposed new value of the property.

In the above example, the value of the weight property is only updated if the value parameter is greater than 0. If you try and update the weight property to a value that’s less than or equal to 0, the setter stops the property from being updated.

A property’s setter runs each time you try to set a property’s value. The following code, for example, calls the weight property’s setter, passing it a value of 75:

myDog.weight = 75

The setter updates the value of the weight property by means of the field identifier. field refers to the property’s backing field, which you can think of as being a reference to the underlying value of the property. Using field in your getters and setters in place of the property name is important, as it stops you getting stuck in an endless loop. When the following setter code runs, for example, the system tries to update the weight property, which results in the setter being called again... and again... and again:

image

The full code for the Dogs project

We’re nearly at the end of the chapter, but before we go, we thought we’d show you the entire code for the Dogs project.

Create a new Kotlin project that targets the JVM, and name the project “Dogs”. Then create a new Kotlin file named Dogs.kt by highlighting the src folder, clicking on the File menu and choosing New → Kotlin File/Class. When prompted, name the file “Dogs”, and choose File from the Kind option.

Next, add the following code to Dogs.kt:

image
image
class Dog(val name: String,
          weight_param: Int,
          breed_param: String) {

    init {
        print("Dog $name has been created. ")
    }

    var activities = arrayOf("Walks")
    val breed = breed_param.toUpperCase()

    init {
        println("The breed is $breed.")
    }

    var weight = weight_param
        set(value) {
          if (value > 0) field = value
        }

    val weightInKgs: Double
        get() = weight / 2.2

    fun bark() {
        println(if (weight < 20) "Yip!" else "Woof!")
    }
}

fun main(args: Array<String>) {
    val myDog = Dog("Fido", 70, "Mixed")
    myDog.bark()
    myDog.weight = 75
    println("Weight in Kgs is ${myDog.weightInKgs}")
    myDog.weight = -2
    println("Weight is ${myDog.weight}")
    myDog.activities = arrayOf("Walks", "Fetching balls", "Frisbee")
    for (item in myDog.activities) {
         println("My dog enjoys $item")
}

    val dogs = arrayOf(Dog("Kelpie", 20, "Westie"), Dog("Ripper", 10, "Poodle"))
    dogs[1].bark()
    dogs[1].weight = 15
    println("Weight for ${dogs[1].name} is ${dogs[1].weight}")
}
image

Test drive

image

When we run the code, the following text gets printed in the IDE’s output window:

Dog Fido has been created. The breed is MIXED.
Woof!
Weight in Kgs is 34.090909090909086
Weight is 75
My dog enjoys Walks
My dog enjoys Fetching balls
My dog enjoys Frisbee
Dog Kelpie has been created. The breed is WESTIE.
Dog Ripper has been created. The breed is POODLE.
Yip!
Weight for Ripper is 15
image

Your Kotlin Toolbox

image

You’ve got Chapter 4 under your belt and now you’ve added classes and objects to your toolbox.

Note

You can download the full code for the chapter from https://tinyurl.com/HFKotlin.

Get Head First Kotlin now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.