1. Previous: Chapter 3
  2. Next: Chapter 5
Kotlin: An Illustrated Guide • Chapter 4

Introduction to Classes and Objects

Chapter cover image

In Chapter 1, we learned about a variety of types in Kotlin, such as Double, String, and Boolean.

Get ready, because in this chapter, we’re going to start creating our very own types! We’ll put variables and functions together into a class.

Let’s get started!

Putting Variables and Functions Together

As you might recall, we started off this book by creating variables to hold things pertaining to a circle, such as its radius and circumference. And in Chapter 2, we created a function to calculate the circumference from the radius.

It all looked kind of scrambled, like this.

Lots of radius and circumference variables for circles of different sizes var radius = 5.2 radius = 10.0 radius = 6.7 val circumferenceOfSmallCircle = 32.65 val circumferenceOfLargeCircle = 62.8 val pi = 3.14 fun circumference(radius) val circumferenceOfMediumCircle = 42.07

This was manageable for such a simple example, but as we come up with more and more things that we want to know about the circle, such as its diameter, area, or position, it can become quite difficult to manage all of those different variables and functions. And once we start introducing other shapes, like rectangles and triangles, it becomes even harder to keep things straight—for example, which functions give us the area of a circle, and which give us the area of a rectangle?

So in this chapter, we’ll create a new type called a Circle. This way, instead of having separate variables and functions that hold a radius and a circumference, we can have a single variable that represents the circle itself.

So instead of the scrambled variables and functions above, it’ll look more like this.

The same variables, but with all of the related variables grouped up according to the circle that it represents. largeCircle val pi = 3.14 val radius = 10.0 fun circumference(radius) mediumCircle val pi = 3.14 val radius = 6.7 fun circumference(radius) smallCircle val pi = 3.14 val radius = 5.2 fun circumference(radius)

In Kotlin, we can put related variables and functions together using a class, which is a feature that we will use to create our new Circle type.

Defining a Class

Let’s create our very first class, a Circle, which will include these variables and functions.

  • The radius variable.

  • The read-only pi variable.

  • The circumference() function.

Instead of jumping into all of this at once, let’s build up this class slowly—one step at a time—and look carefully at each part.

An Empty Class

First, let’s define an empty class—with no variables and no functions.

class Circle

This is all it takes to create a new class called Circle!

When we create a class, we’re creating a new type, just like Int, Double, and String. In other words, once we define the Circle class like this, we can use it anywhere that we’d normally put a type, such as a function parameter.

fun draw(circle: Circle) {
  // Code that draws the circle would go here
}

Our First Diagram

It’s often useful to diagram our classes, so that we can visualize them and communicate our ideas to friends and coworkers. We’ll use diagrams throughout the rest of this book to help explain concepts.

So, as we’re building out this Circle class, we’ll also diagram it at each stage, using a diagramming standard called the Unified Modeling Language, or UML for short.

We’ll start simple. To show a class, just put the name of the class in a box, as we’re doing on the left. On the right is the corresponding Kotlin code.

A very simple UML class diagram for an empty class called Circle class Circle Circle

Now that we’ve got our new Circle type, we could create a variable to hold it, but since our class is completely empty, it’s not particularly useful yet. So, before we do that, let’s give our Circle a radius!

Adding a radius Property

In real life, every circle has a radius, so we need to make sure that every Circle in our code also has a radius variable. Here’s how we can do that.

class Circle(var radius: Double)

In this code, we added a new variable to the class, called radius, which has a type of Double. The value of radius can be changed, because of the var that comes before it. In Kotlin, a variable in a class like this is called a property of the class.

Let’s also update our diagram to show that a Circle class has a radius property. To do this, we draw a horizontal line under the name of the class, and write out the name of the property and its type, almost identically to how we write it in our Kotlin code.

UML class diagram for a Circle with a single property called radius class Circle( var radius : Double) Circle radius: Double

Now that we’ve got a circle class with a radius, we’re ready to start using it!

Objects

In this book, we’ve already created lots of variables. For example, here’s how we can declare and assign a variable with a Double type.

val radiusOfSmallCircle: Double = 5.2

As mentioned, when we created the class, we made a new type called Circle. Just like you can have a variable that’s a Double type, you can also have a variable that’s a Circle type. Declaring and assigning a Circle variable is easy.

val smallCircle = Circle(5.2)

This creates a new variable called smallCircle, which is assigned a Circle with a radius of 5.2.

Keep in mind—just as we can have many different Double values like…

  • 5.2
  • 6.7
  • 10.0

… we can also have lots of different Circle values like…

  • A circle with a radius of 5.2.
  • A circle with a radius of 6.7.
  • A circle with a radius of 10.0.

But when it comes to classes, instead of calling these values, we usually call them objects.

Constructing Objects

Let’s look at that code again.

// Declaring the class
class Circle(var radius: Double)

// Using the class
val smallCircle = Circle(5.2)

What’s happening when we write Circle(5.2)?

It’s kind of like we’re calling a function named Circle() that has a parameter called radius and a return type of Circle. These kinds of functions aren’t called functions, though. They’re called constructors because they construct a new object.

Constructor Parameters

Note that when you call a constructor, you must provide an argument for every property that is listed between the constructor’s opening and closing parentheses—( and ). Since we put var radius: Double between those parentheses, we have to provide an argument of type Double when we call the constructor.

The 5.2 argument is passed to the constructor as the radius parameter class Circle( var radius : Double) val smallCircle = Circle ( 5.2 )

Note that radius is actually filling two roles.

  1. It’s a constructor parameter. We have to provide a value for it whenever we call the constructor. (However, just like with function parameters, we can also give a constructor parameter a default argument.

  2. It’s a property. We’ll be able to get the value of the radius from any circle object. We’ll see an example of this in a moment.

When we create an object, we say that you’re creating an instance of the class. For that reason, creating an object is sometimes referred to as instantiating the class.

Classes vs. Objects

The difference between classes and objects can be confusing at first, so let’s take a moment to clarify it.

A class describes the characteristics and behavior of some concept. If we’re talking about circles, those characteristics might include its radius, diameter, circumference, and area.

An object is an actual, particular instance of that thing. Here are three circle objects.

Circles of different sizes 10.0 5.2 6.7

A circle class answers questions such as these.

  • “What does it mean for something to be a circle?”
  • “What characteristics does it have?”
  • “What does it do?”

A circle object answers questions like these.

  • “What is the radius of this particular circle?”
  • “What is its circumference?”
  • “What is its area?”

Here are a few more examples to help distinguish between classes and objects.

  • We might have a Number class, with objects like 32,768 or 6.62607015.
  • We might have a Color class, with objects like red, green, and blue.
  • We might have a Dog class, with objects like Fido, Rover, or Mrs. Wagglytails.
Multiple dogs, representing multiple objects of the same class.

We’ll see plenty more examples of classes and objects throughout the rest of this book!

Getting a Property’s Value

Now that we’ve created a circle object, how can we get its radius?

Easy—to get the value of a property on an object, type the name of the variable, a dot, and the name of the property, like this.

val smallCircle = Circle(5.2)
val radiusOfSmallCircle: Double = smallCircle.radius

The dot character here is known as the access operator, or more casually, the dot operator.

After running that code, radiusOfSmallCircle will be equal to 5.2. That takes care of radius. It’s time to move onto the other property, pi.

Read-Only Properties

We could put pi inside the parentheses like we did for radius, separating them with a comma.

class Circle(var radius: Double, val pi: Double)

But if we do this, then we’d have to provide pi every time we instantiate a circle!

val smallCircle = Circle(5.2, 3.14)
val mediumCircle = Circle(6.7, 3.14)
val largeCircle = Circle(10.0, 3.14)

That’s not quite what we want. Since pi should always be the exact same value regardless of the particular circle, it doesn’t make sense for it to be a constructor parameter. In fact, it would be better if the calling code could never specify the value of pi when constructing a Circle.

To do that, we can simply move pi out of the parentheses, so that it looks like this.

class Circle(var radius: Double) {
  val pi: Double = 3.14
}

Here, we added an opening brace { and a closing brace }. Everything between those braces is called the body of the class. Inside of the body, we declare pi, and assign it a value of 3.14.

By moving it out of the parentheses, the pi property is no longer a constructor parameter, so we can continue to call the constructor with just the radius, as we did back in Listing 4.5:

val smallCircle = Circle(5.2)

Private Properties

As it’s currently written, we can take any Circle object and get the value of both radius and pi.

val smallCircle = Circle(5.2)

val radiusOfSmallCircle = smallCircle.radius
val piFromSmallCircle = smallCircle.pi

There aren’t too many reasons why code outside of the class would need the value of pi. Let’s make it so that this property is only visible from inside the class. To do that, we’ll add the keyword private when we declare it.

class Circle(var radius: Double) {
  private val pi: Double = 3.14
}

Now, if we try to get the value of the pi property from outside of the class body, we’ll get an error.

val smallCircle = Circle(5.2)

val radiusOfSmallCircle = smallCircle.radius
val piFromSmallCircle = smallCircle.pi
Error

Let’s update our UML class diagram to include the pi property. We can indicate the visibility of a property in the diagram.

  • Private properties are preceded with a - symbol.

  • Public properties are preceded with a + symbol.

UML diagram for a Circle with two properties, including visibility indicators class Circle( var radius : Double) { private val pi : Double = 3.14 fun circumference () = 2 * pi * radius } Circle + radius: Double - pi: Double + circumference(): Double

private is one of a handful of keywords that can be used to tell Kotlin how “visible” we want a property or function to be. By marking pi with private, we made it so that it is only visible inside the body of the class. Any code outside of the class can’t see that property.

A keyword that changes the characteristics of a declared class, property, or function is called a modifier. Since the private modifier controls the visibility, it’s specifically known as a visibility modifier. We’ll see other kinds of modifiers later in this book as we explore other kinds of classes and types.

If we don’t use a visibility modifier, as is the case with radius, then it’s the same thing as marking it as public, which means that we can see that property or function anywhere.

There are two other visibility modifiers—protected and internal.

  • We’ll look at protected in detail in Chapter 14, once we learn about at subclasses.

  • We won’t cover internal in this book, but it’s used to restrict the visibility to a single library of code.

Now, we’re ready to add the circumference() function to the class!

Adding a Member Function

When a function belongs to a class, it’s often called a method or member function. Adding a method to a class is easy—simply put it into the body of the class. To start with, let’s just take the exact same function from Listing 2.3, and drop it verbatim into our Circle class.

class Circle(var radius: Double) {
  private val pi: Double = 3.14

  fun circumference(radius: Double) = 2 * pi * radius
}

When first we created the circumference() function back in Chapter 2, it made sense for it to have a parameter named radius. But now that we’re adding this function to the class, we can just refer to the value of the radius property instead.

In other words, instead of referring to the radius parameter, like this…

The radius inside the function body refers to the radius argument of the circumference function. class Circle( var radius : Double) { private val pi : Double = 3.14 fun circumference (radius: Double) = 2 * pi * radius }

… we can remove the radius parameter from the function, so that it refers to the property, like this.

The radius inside the function body refers to the radius argument of the circumference function. class Circle( var radius : Double) { private val pi : Double = 3.14 fun circumference () = 2 * pi * radius }

This introduces the concept of scope, which we will explore in depth in Chapter 11. For now, just be sure to remove the parameter from the circumference() function so that 2 * pi * radius will refer to the radius property.

class Circle(var radius: Double) {
  private val pi: Double = 3.14

  fun circumference() = 2 * pi * radius
}

Now that Circle has a circumference() function, how can we call it?

Calling a function on an object is done similarly to how we got the value of radius: the name of the variable, a dot, and the name of the function.

val smallCircle = Circle(5.2)
val circumferenceOfSmallCircle: Double = smallCircle.circumference()

Before we move on, let’s add circumference() to our diagram!

UML class diagram with two properties and a function. class Circle( var radius : Double) { private val pi : Double = 3.14 fun circumference () = 2 * pi * radius } Circle + radius: Double - pi: Double + circumference(): Double

Note that the diagram does not include the body of the function. In other words, 2 * pi * radius does not appear in it. That’s because class diagrams are designed to give you an idea of what data and behavior are a part of the class, without going into the specifics.

Adding More Functions

When we started off this chapter, we only had a radius variable and a circumference() function. Now that we’ve put those two things together into a class, it’s time to fill out our Circle class with other things that we might want to know about a circle.

For example, we can add a function that calculates the area of the circle.

class Circle(var radius: Double) {
  private val pi: Double = 3.14

  fun circumference() = 2 * pi * radius
  fun area() = pi * radius * radius
}

val smallCircle = Circle(5.2)
val areaOfSmallCircle = smallCircle.area()

We can also add a function to calculate its diameter.

class Circle(var radius: Double) {
  private val pi: Double = 3.14

  fun circumference() = 2 * pi * radius
  fun area() = pi * radius * radius
  fun diameter() = 2 * radius
}

val smallCircle = Circle(5.2)
val diameterOfSmallCircle = smallCircle.diameter()

And, we can even call the diameter() function from inside circumference().

class Circle(var radius: Double) {
  private val pi: Double = 3.14

  fun circumference() = diameter() * pi
  fun area() = pi * radius * radius
  fun diameter() = 2 * radius
}

And now, we can add these last few functions to our UML diagram.

UML class diagram for a Circle with multiple properties and functions class Circle( var radius : Double) { private val pi : Double = 3.14 fun circumference () = diameter () * pi fun area () = pi * radius * radius fun diameter () = 2 * radius } Circle + radius: Double - pi: Double + circumference(): Double + area(): Double + diameter(): Double

Anatomy of a Class

Now that we’ve covered the basics of Kotlin classes, here’s a recap of the main pieces.

Anatomy of a class in Kotlin class Circle( var radius : Double) { private val pi : Double = 3.14 fun circumference () = 2 * pi * radius } val smallCircle = Circle ( 5.2 ) name of class keyword function variable holding an object constructor argument property as constructor parameter constructor call property that is not a constructor parameter

There are a few important terms to know before we move on.

  • Properties and functions declared within a class are regarded as members of the objects created from that class.

  • Variables and functions that are declared within a function are said to be local to that function, because you can only use them inside that function’s body.

  • The term top-level is used to refer to a variable or function that is neither a member of a class nor declared within a function.

Everything is an Object

Up until this chapter, we’ve only used built-in Kotlin types, such as Double, String, and Boolean. You might be surprised to learn that when we used those types, we were actually using classes and objects! Just like we used the dot to get properties and call functions on our Circle objects, we can also use a dot on any of these types. Let’s look at a few examples of member functions and properties on types that we’ve seen before.

Doubles as Objects

Objects of type Double have functions like plus() and times(). So instead of writing our circumference() function like this…

fun circumference() = 2 * pi * radius

… we can instead write it like this.

fun circumference() = 2.times(pi).times(radius)

Kotlin developers normally use the arithmetic operators (+, -, *, /) in most cases, but the functions are there if you want them!

Strings as Objects

String objects also have some interesting properties and functions. Here are a few examples.

  • The length property tells you how many characters (i.e., letters, numbers, and symbols) are in the string.

  • uppercase() will force all of the letters in the string to upper case.

  • drop() will remove characters from the beginning of the string.

Here’s how that looks in code.

val greeting: String = "Welcome"

val numberOfLettersInGreeting = greeting.length // Evaluates to 7
val loudGreeting = greeting.toUpperCase()       // Evaluates to "WELCOME"
val substring = greeting.drop(3)                // Evaluates to just "come"

Booleans as Objects

Even Boolean variables—which are only ever true or false—are objects! For example, if you want to turn on the headlights of your car if either it’s dark or it’s raining, you can write code to do that like this.

val isDark: Boolean = true
val isRaining: Boolean = false

val shouldTurnOnHeadlights = isDark.or(isRaining)
val shouldStayHome = isDark.and(isRaining)

Although it’s possible to use functions like or() and and() on a Boolean variable, it’s usually a better idea to use the operators || and && instead, like this:

val shouldTurnOnHeadlights = isDark || isRaining // Evaluates to true
val shouldStayHome = isDark && isRaining         // Evaluates to false

The fancy words for || and && are disjunction operator and the conjunction operator, respectively, but almost all programmers just call them “or” and “and”. The reason to favor the operators over the function calls has to do with a concept called short-circuiting, and here’s how it works.

  • When using ||, if the expression on the left evaluates to true, Kotlin knows that the result of the whole thing must be true, so it won’t bother evaluating the expression on the right.

  • When using &&, if the expression on the left evaluates to false, Kotlin knows that the result of the whole thing must be false, so it won’t bother evaluating the expression on the right.

When we’re just writing a simple case like above, where we’ve got two Boolean variables, this won’t make much of a difference, but if we’ve got a function call that takes a long time to calculate its result this could be a big deal.

val shouldGetRaise = yearsOfService > 1 && calculateSalary() < maximumSalary

Boolean values, conjunction, and disjunction are all part of the wonderful world of Boolean algebra.

Single-Instance Objects

Let’s imagine that we’ve created classes for a few more shapes, such as triangles and rectangles. If we want a function that prints each kind, we could end up with lots of functions that aren’t grouped together in any way.

fun printCircle(circle: Circle) =
    println("This circle has a radius of ${circle.radius}")

fun printTriangle(triangle: Triangle) =
    println("This triangle has an area of ${triangle.area}")

fun printRectangle(rectangle: Rectangle) =
    println("This rectangle has a perimeter of ${rectangle.perimeter}")

Much like at the beginning of this chapter, we end up with multiple functions that are related, but there’s nothing grouping them together.

fun printCircle(circle: Circle) fun printRectangle(rectangle: Rectangle) fun printTriangle (triangle: Triangle)

Of course, we could use a class to group them, as we did with Circle back in Listing 4.19.

class ShapePrinter {
    fun printCircle(circle: Circle) = ...
    fun printTriangle(triangle: Triangle) = ...
    fun printRectangle(rectangle: Rectangle) = ...
}

val printer = ShapePrinter()
val circle = Circle(5.2)
printer.printCircle(circle)

This certainly works, but there are some important differences between the Circle class and this ShapePrinter class.

For example, it made sense for a Circle to be a class, because each instance might have a different radius value. However, ShapePrinter has no constructor parameters. In fact, we could easily imagine just using the same, single instance of ShapePrinter everywhere throughout our code.

For cases like this, instead of defining this as a class and then instantiating it into an object, we can simply declare it as an object directly.

To do this, we can use the object keyword rather than the class keyword.

object ShapePrinter {
    fun printCircle(circle: Circle) = ...
    fun printTriangle(triangle: Triangle) = ...
    fun printRectangle(rectangle: Rectangle) = ...
}

val circle = Circle(5.2)
ShapePrinter.printCircle(circle)

When we define an object with the object keyword, there will only ever be a single instance of that object. In many programming languages, this is known as a singleton. As demonstrated in the code above, a function or property in a singleton can be called by using its type name, a dot, and the name of the member.

ShapePrinter. printCircle (circle) Type name Dot Member name

Note that there’s no constructor for us to call here, because it’s an object rather than a class. Types declared with the object keyword can have properties, but they can’t have constructor properties, because there’s no constructor for us to call!

Grouping into Packages

We’ve seen how we can group properties and functions into classes and singleton objects. However, it doesn’t end here—we can group our code even further!

When we look at the contents of a computer’s hard drive, we’ll see lots of different files. To keep these files organized, they’re also separated into folders. We might even include one folder inside of another folder. This structure creates a hierarchy that keeps related files together, making it easier for us to know where to look when we’re trying to find a file.

Documents Files and Folders Paths /Documents /Documents/ThingsToDo.txt /Documents/Spreadsheets /Documents/Spreadsheets/Financial.spr ThingsToDo.txt Financial.spr Spreadsheets

Similarly, our Kotlin code can be broken up into separate files and folders. Whereas files and folders are a concept that applies to the computer’s operating system, Kotlin deals with the concepts of *code elements—things like variables, functions, objects, and classes—and packages.

shapes Files and Folders Code Elements and Packages package shapes fun main() package shapes.circle val pi class Circle Main.kt Circle.kt circle

Generally, there’s a one-to-one relationship between a project’s folders and its packages. When we spread out our Kotlin code across different folders, we should also indicate the package that corresponds to its folder. Package names are similar to the folder paths in the operating system, but instead of separating them with slashes, we separate them with dots.

/shapes /shapes/circle /shapes/rectangle /shapes/triangle Path Corresponding Package shapes shapes.circle shapes.rectangle shapes.triangle

To indicate the package name for a Kotlin file, use the keyword package followed by the name of the package. For example, if our Circle class is in a file in the /shapes/circle folder, then at the top of the file, it should include “package shapes.circle“.

package shapes.circle

val pi: Double = 3.14

class Circle(var radius: Double) {
  fun circumference() = diameter() * pi
  fun area() = pi * radius * radius
  fun diameter() = 2 * radius
}

By adding this line, all of the code elements within that file will be included in the shapes.circle package.

shapes.circle val pi class Circle

One of the nice things about packages is that we can have multiple code elements with the same name in different packages.

package shapes.circle val shape = Circle ( radius = 5.2 ) package shapes.rectangle val shape = Rectangle ( width = 2.8 , height = 4.5 ) Different packages Same variable name

If two code elements can have the same name, how can Kotlin distinguish between them?

Qualifying Code Elements

Until we started explicitly declaring the packages, all of our code elements were in the same default package. When an element is declared in the same package where we’re using it, we can just refer to it with its simple name.

For example, let’s say our shape variable is in the same package as our main() function. In that case, we can simply refer to it as shape.

package shapes.circle val shape = Circle ( radius = 5.2 ) package shapes.circle fun main () { val circle = shape } Same package Simple name

However, if we want to use a code element that’s declared in a different package, we’ll need to qualify its name to clarify exactly which one we’re talking about. For example, if we move the main() function into the shapes package instead of the shapes.circle package, we can refer to it by using its fully-qualified name, which includes its full package name, a dot, and its simple name.

package shapes fun main () { val circle = shapes.circle. shape } Fully-qualified name

In this code, you might have noticed that the shape variable is in a deeper package within the shapes package, where the main() function resides. In cases like this, rather than using the fully-qualified name, we can use a partially-qualified name, which looks like this.

package shapes fun main () { val circle = circle. shape } Partially-qualified name

Writing out qualified names can be a lot of typing, and they can take up a lot of space in the code, so let’s look at a more common way to use code elements from other packages.

Importing Code Elements

Rather than qualifying a code element everywhere that we use it, we can import it at the beginning of the file.

For example, let’s import the shape variable from the shapes.circle package. To import a code element, simply use the keyword import and its fully-qualified name. Imports are placed after the package declaration and before the rest of your code in the file, as seen here.

package shapes

import shapes.circle.shape

fun main() {
    val circle = shape
}

By importing shape from the shapes.circle package, we no longer need to qualify the name when we use it.

Named Imports

In some cases, we might want to import two code elements that have the same name. For example, if we try to import shape from both the shapes.circle and the shapes.rectangle packages, we’ll get an error.

package shapes import shapes.circle. shape import shapes.rectangle. shape fun main () { val myShape = shape } Which of these two should this refer to?

In these cases, we can use a named import for one or both of the elements we’re importing, which effectively gives the element an alias in that file. For example, let’s use a named import for shapes.circle.shape.

package shapes import shapes.circle. shape as circle import shapes.rectangle. shape fun main () { val myShape = circle val myOtherShape = shape } Named import

Wildcard Imports

In bigger projects, it’s not unusual for a file to include a long list of imports. If you find yourself importing lots of elements from the same package, you can use a wildcard import instead. For example, we might need to import Circle, shape, and pi from the shapes.circle package. We could import all three of them, like this.

package shapes

import shapes.circle.Circle
import shapes.circle.shape
import shapes.circle.pi

fun main() {
    println(Circle(1.9).radius)
    println(shape.radius)
    println(pi)
}

However, we can also use an asterisk * wildcard.

package shapes

import shapes.circle.*

fun main() {
    println(Circle(1.9).radius)
    println(shape.radius)
    println(pi)
}

Just be mindful that wildcards make it easy to import more than you expect, and could result in naming collisions that you might not have anticipated. In fact, some developers prefer never to use wildcard imports at all!

Importing from the Standard Library

Packages and imports aren’t useful just for your own code—sometimes they’re needed when using functions or types from Kotlin’s standard library. For example, the standard library includes its own definition of pi, so we don’t have to define it ourselves.

We can update our Circle class to use Kotlin’s pi like this.

import kotlin.math.PI

class Circle(var radius: Double) {
    fun circumference() = diameter() * PI
    fun area() = PI * radius * radius
    fun diameter() = 2 * radius
}

In fact, even println() is defined in a package named kotlin.io. However, we don’t have to import println() because Kotlin automatically imports everything from the kotlin.io package, as well as a few other packages in the standard library.

Most of the code in this book is focused and self-contained such that we usually won’t need to declare packages or imports. It’s important to know about them, though, because they’re very frequently needed in real-world projects.

Summary

Enjoying this book?
Order the paperback today!

Kotlin: An Illustrated Guide is now available on Amazon See the book on Amazon

Classes are powerful, and even though we covered a lot of ground in this chapter, we’ve really only introduced them. They open up a whole new world of ways to represent concepts in our code. Later in this book, we’ll cover more advanced concepts related to classes, such as inheritance.

Here’s what we learned in this chapter:

Now that we’ve covered the basics of classes, it’s time to explore a special kind of class in Kotlin: an enum class. In the next chapter, we’ll see how enum classes can be used to limit our options—and why that’s a good thing!

Thanks to Tobenna Ezike and Esraa Ibrahim for reviewing this chapter.

Share this article:

  • Share on Facebook
  • Share on Reddit