A class is a blueprint for an object. In this unit, learn how to write Kotlin classes, which includes learning about Kotlin coding conventions, what goes in a source file, how to write classes and subclasses, and much more.

Be sure to watch the video at the end of the unit, where I show you how to create a project in IntelliJ IDEA for the unit’s source code and how to run the examples.

Unit objectives

In this unit, you will:

  • Understand Kotlin coding conventions
  • Understand the structure of a Kotlin source file
  • Understand the structure of a Kotlin class
  • Learn how to declare a class
  • Learn how to instantiate a class to create an object
  • Learn how to override a function from a parent class
  • Learn how to extend a parent class to create a subclass
  • Learn about Kotlin visibility modifiers

Source code for this unit

The source code for this unit is in the Kotlin/Unit_6 directory of the GitHub repository for the learning path. Please take a minute to clone the repository so that you can follow along with the learning path to get the most out of the material.

You may notice that some of the code examples are labeled as a Listing and some as an Example:

  • A Listing is a block of code with a number (Listing 1, for example) that is used to refer back to itself later in the text.

  • An Example is code from the learning path repository with the same example number as in the repository; Example 1 refers to source code in src/example1, and so on.

Kotlin coding conventions

Coding conventions are guidelines for how source code should be written to ensure consistency among developers, maximum readability, and ease of maintenance. The idea is this: If every developer follows the coding conventions, another one will be able to read, understand, and maintain the code.

The Kotlin designers have published their recommendations for writing Kotlin code and you will follow them throughout the learning path.

Kotlin source files

Kotlin source code is stored in files like any other programming language. So far in the learning path, you have worked with the REPL to write quick and dirty programs to learn various concepts. In this unit (and going forward), you will use your IDE to write and store code in files and run and keep track of your projects.

Continue to use IntelliJ IDEA Community Edition for the learning path, since it will be featured in all of the examples in this unit.

The name of a Kotlin source file should be indicative of its contents, along with the extension kt (short for Kotlin). For example, if the source file contains a class called Jabberwocky, call the file Jabberwocky.kt.

Use camel case — writing several words as one with an initial capital letter for each word — if the name contains multiple words. For example, the concept of “human resources collection” would be named HumanResourcesCollection.kt.

Source files include elements like:

  • package declaration
  • import statements
  • class declarations
  • Function (fun) declarations

You can declare other structural components, but I won’t cover them here. If you want to see the complete list, check out the Kotlin grammar specification.

Packages

A package is a way to group similar classes and functions together into a namespace:

  • Within a package, all top-level element names must be unique

  • Outside of the package (for example, in a different package), a name may be repeated

For example, within package org.acme.a, you can only have one publicly visible class named Foo, but package org.acme.b may also contain a public class called Foo.

The directory structure must match the package structure in a Java-only or mixed-language Java/Kotlin project.

Suppose you have a package com.makotogo.learn.kotlin and it contains subpackages example1, example2, and so forth. The directory structure would look like this:

com
└── makotogo
    └── learn
        └── kotlin
            └── src
                ├── example1
                │   └── Person.kt
                ├── example2
                │   └── Person.kt
                ├── example3
                │   └── Person.kt
                └── example4
                    ├── Family.kt
                    └── Neighbor.kt

All of the source in this unit will share the com.makotogo.learn.kotlin root package. The directory structure looks like this:

Unit_6
└── src
    ├── example1
    │   └── Person.kt
    ├── example2
    │   └── Person.kt
    ├── example3
    │   └── Person.kt
    └── example4
        ├── Family.kt
        └── Neighbor.kt

The Kotlin project convention is to branch into different subdirectories when the package names diverge from the root; that is, the src directory.

Using the Kotlin convention, the directory tree is cleaner because all source code shares the com.makotogo.learn.kotlin package root.

The package structure is reflected in the directory structure when the .class files (the bytecode) are generated:

Unit_6/out/
└── production
    └── Unit_6
        ├── META-INF
        │   └── Unit_6.kotlin_module
        └── com
            └── makotogo
                └── learn
                    └── kotlin
                        ├── example1
                        │   ├── Person.class
                        │   └── PersonKt.class
                        ├── example2
                        │   ├── Person.class
                        │   └── PersonKt.class
                        ├── example3
                        │   ├── Person.class
                        │   └── PersonKt.class
                        └── example4
                            ├── Child.class
                            ├── FamilyKt.class
                            ├── InternalClass.class
                            ├── NeighborKt.class
                            ├── Parent.class
                            └── PrivateClass.class

Imports

The import keyword is used so that your code can reference code from other packages in your project, modules in your source tree, and other Kotlin and Java libraries.

The import statement looks like:

import java.time.LocalDate

The java.time.LocalDate class is part of the Java runtime. The import statement tells the compiler to look for this class so your code will compile.

You’ll see the import statement later in this unit in Example 3.

Classes

You learned in Unit 3 that a class is a blueprint for an object and that it “describes the object’s structure” in the form of properties (attributes) and functions (behavior).

The basics

The simplest class declaration looks like this:

class Person

Thanks to Kotlin’s compact syntax, this is actually a functional class (as you’ll see later).

Add attributes

This very simple class doesn’t do much, so let’s add a few attributes to the Person class:

import java.time.LocalDate

class Person(val givenName: String, val familyName: String, val dateOfBirth: LocalDate)

Listing 1. A Kotlin class with three attributes

The Person class has three publicly visible attributes:

  • givenName of type String
  • familyName of type String
  • dateOfBirth of type java.time.LocalDate

The Kotlin compiler automatically generates two things to declare on the Person class:

All of this fits on one line of code. Compare it to its Java equivalent:

import java.time.LocalDate

public final class Person {
    private String givenName;
    private String familyName;
    private LocalDate dateOfBirth;

    public String getGivenName() {
        return this.givenName;
    }

    public String getFamilyName() {
        return this.familyName;
    }

    public LocalDate getDateOfBirth() {
        return this.dateOfBirth;
    }

    public Person(String givenName, String familyName, LocalDate dateOfBirth) {
        this.givenName = givenName;
        this.familyName = familyName;
        this.dateOfBirth = dateOfBirth;
    }
}

Listing 2. The Java language equivalent of the Kotlin Person class from Listing 1

If you compare Listings 1 and 2, you’ll see that Kotlin is lean indeed!

Objects

Recall from Unit 3 that an object is an instance of a class. Add a main() function to the basic Person class so you can run it in the IDE:

1 package com.makotogo.learn.kotlin.example1
2
3 class Person
4
5 fun main(args: Array<String>) {
6     val person = Person()
7
8     println("Person: $person")
9 }

Example 1. The simplest class declaration with a main() function to drive it

Note: You can find the code for Example 1 the Kotlin/Unit_6/src/example1 directory.

Use Person‘s generated constructor to create the person object (line 6). Then pass it to println() (line 8) where the Person.toString() function is called (you’ll see this later).

Run the main() function in IntelliJ and you’ll see something like in Figure 1.

image

Figure 1. Run Person.main() in IntelliJ

Note: In addition to the output from the Kotlin program you’re running (the “program output”), you can see output from IntelliJ (the first, second, and last lines). Going forward, I’ll only show program output for the sake of brevity.

Notice this line in the output pane:

Person: com.makotogo.learn.kotlin.example1.Person@27c170f0
What’s with that weird looking number?
Every Kotlin object has a string representation that is generated by the toString() function (more on this later), which consists of the object’s fully qualified class name (package name plus class name) and its hash code (that weird looking hexadecimal number after the @ symbol). An object’s hash code is a 32-bit number that uniquely represents the object in the JVM. Don’t worry about it for now. You’ll see hash code again in Unit 9.

It may not seem very interesting, but there’s actually a lot going on here. Let’s break it down.

First, there is the package declaration:

package com.makotogo.learn.kotlin.example1

This declares a package called com.makotogo.learn.kotlin.example1 in which the Person class resides.

You’ve seen the declaration of the Person class: class Person.

Then there’s the main() function, which is a special function that the Kotlin compiler uses to generate an entry point for the Person class (so you can run it). The first line of main() contains this line of code:

    val person = Person()

This line of code instantiates the Person class to become a Person object.

The Person object is then passed as a parameter to println function where its default string representation is printed to the console.

Add a main() function to the Person class from Listing 1 that generates a better string representation of the Person object:

package com.makotogo.learn.kotlin.example2

import java.time.LocalDate

class Person(val givenName: String, val familyName: String, val dateOfBirth: LocalDate)

fun main(args: Array<String>) {
    val person = Person(
            "Susan",
            "Neumann",
            LocalDate.of(1980, 3, 17) // 17 Mar 1980
    )

    println("Person: Family Name=${person.familyName}, " +
            "Given Name=${person.givenName}, " +
            "Date of Birth=${person.dateOfBirth}")
}

Example 2. The Person class, complete with main function to drive it

Note: You can find the code for the above listing in the Kotlin/Unit_6/src/example2 directory.

When you run the main() function, the output looks like this:

Person: Family Name=Neumann, Given Name=Susan, Date of Birth=1980-03-17

The code that generates the string representation of the Person object resides in the main() function. This is not very object oriented, but you’ll learn how to improve this situation shortly.

Override a function

One of the functions provided by the Any class is toString(), whose default implementation is the name of the class. Its hash code looks like this:

com.makotogo.learn.kotlin.example3.Person@27c170f0
The Any class
The Any class class is at the root of the Kotlin hierarchy. It corresponds to Java’s Object class and every Kotlin class you’ll ever write inherits from Any.

In Example 3, you’ll see how to override the toString() function and move the code there from main() to generates the string representation of a Person object:

package com.makotogo.learn.kotlin.example3

import java.time.LocalDate

class Person(val givenName: String, val familyName: String, val dateOfBirth: LocalDate) {
    override fun toString(): String {
        return "[givenName=$givenName, familyName=$familyName, dateOfBirth=$dateOfBirth]"
    }
}

fun main(args: Array<String>) {
    val person = Person(
            "Susan",
            "Neumann",
            LocalDate.of(1980, 3, 17)
    )
    println(person)
}

Example 3. Person class with overridden toString() function

Run Example 3 in IntelliJ and the output will look like this:

[givenName=Susan, familyName=Neumann, dateOfBirth=1980-03-17]

Now the code that shows the Person object’s attribute values is part of the class itself.

Exercise 1

Remove the override keyword from the toString() function in Example 3. What happens? Can you explain why?

Check your answer

When you remove the override keyword from the toString() function, the compiler complains with this message:

Error:(6, 9) Kotlin: 'toString' hides member of supertype 'Any' and needs 'override' modifier

In Java, you can override a class method without providing any annotation (the @Override annotation is recommended, but not required).

In Kotlin, if you want to override a function from a superclass (that is, a parent class), the override keyword is required (and the superclass must be declared open).


Open classes

Kotlin classes are final by default, an intentional language design choice called closed inheritance.

The Kotlin philosophy is that:

Inheritance is a design choice you make deliberately, and so you should have to indicate this when you create a class by using the open keyword.

A Kotlin class that is declared open is called an open class.

In Java, inheritance is open by default, meaning that by default any Java class can be subclassed. This can lead to problems like the fragile base class problem where simple changes to the base class can lead to bugs in child classes.

This Q&A explains the good reasons to restrict inheritance in Java.

Visibility modifiers

Visibility modifiers limit where the following elements can be seen:

  • Classes
  • Functions
  • Objects
  • Constructors
  • Properties
  • Interfaces

The default visibility modifier is public.

Kotlin’s visibility modifiers affect all of the elements of the language in the list, but we’ll only talk about classes and functions in this unit.

The visibility modifiers are summarized in Table 1.

Modifier Is visible
public Everywhere (default)
internal Only within the module where it is defined
protected Only within the file where it is defined, plus subclasses
private Only within the file where it is defined

Let’s look at a few examples from the Family.kt file, part of Example 4.

01 package com.makotogo.learn.kotlin.example4
02
03 var publicProperty: String = "publicPackageProperty"
04 internal var internalProperty: String = "internalPackageProperty"
05 private var privateProperty: String = "privatePackageProperty"
06
07 private class PrivateClass(val name: String)
08
09 internal class InternalClass(val name: String)
10
11 open class Parent(val name: String) {
12     private val privateClass = PrivateClass("$name.privateClass")
13     internal val internalClass = InternalClass("$name.internalClass")
14     protected val protectedString = "$name.protectedString"
15
16     protected fun disclosePrivate() {
17         println("${privateClass.name}")
18     }
19 }
20
21 private class Child(name: String) : Parent(name) {
22     fun disclose() {
23         println("$protectedString")
24         disclosePrivate()
25     }
26 }

Let’s walk through this code, and I’ll explain what it’s doing.

Lines What it is doing
1 Creates a package for the code.
3-5 Create public, internal, and private properties, which are visible anywhere in the Family.kt file.
7 Declares a private class called PrivateClass with one public property: name. This class is visible anywhere in the Family.kt file (but only within that file).
9 Declares an internal class called InternalClass with one public property: name. This class is visible anywhere in the Unit_6 module.
11-19 Declare an open, public parent class, visible everywhere, with properties of various visibility types, including a protected function to print the privateClass value.
21-26 Declare a private class that extends the open parent class. This class is visible only within Family.kt.

The main() function to drive the code in the Family.kt file looks like this:

fun main(args: Array<String>) {
    println("$publicProperty")
    println("$internalProperty")
    println("$privateProperty")

    val parent = Parent("Parent")
    println("${parent.name}")
    println("${parent.internalClass.name}")

    val child = Child("Child")
    println("${child.name}")
    println("${child.internalClass.name}")
    child.disclose()
}

main() can access all top-level declarations (publicProperty, internalProperty, Parent, and so forth) and print their values to the console because it lives in the same file as these declarations.

The complete code listing is shown in Example 4.

package com.makotogo.learn.kotlin.example4

var publicProperty: String = "publicPackageProperty"
internal var internalProperty: String = "internalPackageProperty"
private var privateProperty: String = "privatePackageProperty"

internal class InternalClass(val name: String)
private class PrivateClass(val name: String)

open class Parent(val name: String) {
    private val privateClass = PrivateClass("$name.privateClass")
    internal val internalClass = InternalClass("$name.internalClass")
    protected val protectedString = "$name.protectedString"

    protected fun disclosePrivate() {
        println("${privateClass.name}")
    }
}

private class Child(name: String) : Parent(name) {
    fun disclose() {
        println("$protectedString")
        disclosePrivate()
    }
}

fun main(args: Array<String>) {
    println(publicProperty)
    println(internalProperty)
    println(privateProperty)

    val parent = Parent("Parent")
    println(parent.name)
    println(parent.internalClass.name)

    val child = Child("Child")
    println(child.name)
    println(child.internalClass.name)
    child.disclose()
}

Example 4. The Family.kt file in src/example4

Open Family.kt, located in the src/example4 directory, and run the main() function. The output looks like this:

publicPackageProperty
internalPackageProperty
privatePackageProperty
Parent
Parent.internalClass
Child
Child.internalClass
Child.protectedString
Child.privateClass

Open Neighbor.kt in the src/example4 directory:

package com.makotogo.learn.kotlin.example4

fun main(args: Array<String>) {
    println(publicProperty)
    println(internalProperty)

    val internalClass = InternalClass("Neighbor.internalClass")
    println(internalClass.name)

    val parent = Parent("Parent")
    println(parent.name)
    println(parent.internalClass.name)
}

Example 4. Neighbor.kt

Neighbor.kt is declared with the same package as Family.kt but in a different file. This means the private declarations in Family.kt are inaccessible to it. Run Neighbor.kt‘s main function and you’ll see this output:

publicPackageProperty
internalPackageProperty
Neighbor.internalClass
Parent
Parent.internalClass

The public elements are available:

  • publicProperty (prints “publicPackageProperty“)
  • Parent

The internal elements are visible too because Neighbor.kt is in the same module as Family.kt:

  • internalProperty (prints “internalPackageProperty“)
  • InternalClass

However, the private elements are not visible because Neighbor.kt is in a different file than Family.kt:

  • privateProperty
  • PrivateClass
  • Child

Exercise 2

Study the main function in Family.kt and then run it. Can you explain the output that you see?

Check your answer

First, main prints the publicPackageProperty, internalPackageProperty, and privatePackageProperty values.

Then the Parent class is instantiated and the name property (which is public) is accessed and its value printed, along with Parent‘s internalClass.name property.

Finally, the Child class is instantiated and the Child object’s name property and internalClass.name property (both of which it inherits from Parent) values are printed.

Next, the value of the protectedString property from the Parent class is printed.

Finally, the disclose() function is invoked which prints the name of the object, a dot, and the name of the property (Child.privateClass).


Conclusion

In this unit, you discovered:

  • Kotlin coding conventions
  • The structure of a Kotlin source file
  • The structure of a Kotlin class – that is, what goes in it
  • How to declare a class
  • How to create an object by instantiating a class
  • How to create a subclass by extending a parent class
  • How to override a function from a parent class
  • Kotlin’s visibility modifiers

Video

In the video, I’ll show you how to:

  • Create a project for Unit 6
  • Run Examples 1-4

Previous: More Kotlin basicsNext: All about functions