The Basics of Object-Oriented Programming in Kotlin

Kotlin provides extensive support for developing object-oriented applications. The subject area of object-oriented programming is, however, large. As such, a detailed overview of object-oriented software development is beyond the scope of this book. Instead, we will introduce the basic concepts involved in object-oriented programming and then move on to explain the concept as it relates to Kotlin application development.

What is an object?

Objects (also referred to as instances) are self-contained modules of functionality that can be easily used and re-used as the building blocks for a software application.

Objects consist of data variables (called properties) and functions (called methods) that can be accessed and called on the object or instance to perform tasks and are collectively referred to as class members.

What is a class?

Much as a blueprint or architect’s drawing defines what an item or a building will look like once it has been constructed, a class defines what an object will look like when it is created. It defines, for example, what the methods will do and what the properties will be.

Declaring a Kotlin class

Before an object can be instantiated, we first need to define the class ‘blueprint’ for the object. In this chapter, we will create a bank account class to demonstrate the basic concepts of Kotlin object-oriented programming.

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

In declaring a new Kotlin class we specify an optional parent class from which the new class is derived and also define the properties and methods that the class will contain. The basic syntax for a new class is as follows:

class NewClassName: ParentClass {
   // Properties
   // Methods
}Code language: Kotlin (kotlin)

The Properties section of the declaration defines the variables and constants that are to be contained within the class. These are declared in the same way that any other variable would be declared in Kotlin.

The Methods sections define the methods that are available to be called on the class and instances of the class. These are essentially functions specific to the class that perform a particular operation when called upon and will be described in greater detail later in this chapter.

To create an example outline for our BankAccount class, we would use the following:

class BankAccount {
 
}Code language: Kotlin (kotlin)

Now that we have the outline syntax for our class, the next step is to add some properties to it.

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

Adding properties to a class

A key goal of object-oriented programming is a concept referred to as data encapsulation. The idea behind data encapsulation is that data should be stored within classes and accessed only through methods defined in that class. Data encapsulated in a class are referred to as properties or instance variables.

Instances of our BankAccount class will be required to store some data, specifically a bank account number and the balance currently held within the account. Properties are declared in the same way any other variables are declared in Kotlin. We can, therefore, add these variables as follows:

class BankAccount {
    var accountBalance: Double = 0.0
    var accountNumber: Int = 0
}Code language: Kotlin (kotlin)

Having defined our properties, we can now move on to defining the methods of the class that will allow us to work with our properties while staying true to the data encapsulation model.

Defining methods

The methods of a class are essentially code routines that can be called upon to perform specific tasks within the context of that class.

Methods are declared within the opening and closing braces of the class to which they belong and are declared using the standard Kotlin function declaration syntax.

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

For example, the declaration of a method to display the account balance in our example might read as follows:

class BankAccount {
    var accountBalance: Double = 0.0
    var accountNumber: Int = 0
 
    fun displayBalance()
    {
       println("Number $accountNumber")
       println("Current balance is $accountBalance")
    }
}Code language: Kotlin (kotlin)

Declaring and initializing a class instance

So far, all we have done is define the blueprint for our class. To do anything with this class, we need to create instances of it. The first step in this process is to declare a variable to store a reference to the instance when it is created. We do this as follows:

val account1: BankAccount = BankAccount()Code language: Kotlin (kotlin)

When executed, an instance of our BankAccount class will have been created and will be accessible via the account1 variable. Of course, the Kotlin compiler will be able to use inference here, making the type declaration optional:

val account1 = BankAccount()Code language: Kotlin (kotlin)

Primary and secondary constructors

A class will often need to perform some initialization tasks at the point of creation. These tasks can be implemented using constructors within the class. In the case of the BankAccount class, it would be useful to be able to initialize the account number and balance properties with values when a new class instance is created. To achieve this, a secondary constructor can be declared within the class header as follows:

class BankAccount {
    
    var accountBalance: Double = 0.0
    var accountNumber: Int = 0
    
    constructor(number: Int, balance: Double) {
        accountNumber =  number
        accountBalance = balance
    }
.
.
}Code language: Kotlin (kotlin)

When creating an instance of the class, it will now be necessary to provide initialization values for the account number and balance properties as follows:

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

val account1: BankAccount = BankAccount(456456234, 342.98)Code language: Kotlin (kotlin)

A class can contain multiple secondary constructors allowing instances of the class to be initiated with different value sets. The following variation of the BankAccount class includes an additional secondary constructor for use when initializing an instance with the customer’s last name in addition to the corresponding account number and balance:

class BankAccount {
    
    var accountBalance: Double = 0.0
    var accountNumber: Int = 0
    var lastName: String = ""
    
    constructor(number: Int, 
                balance: Double) {
        accountNumber =  number
        accountBalance = balance  
    }
    
    constructor(number: Int, 
                balance: Double,
               name: String ) {
        accountNumber =  number
        accountBalance = balance
        lastName = name
    }
.
.
}Code language: Kotlin (kotlin)

Instances of the BankAccount may now also be created as follows:

val account1: BankAccount = BankAccount(456456234, 342.98, "Smith")Code language: Kotlin (kotlin)

It is also possible to use a primary constructor to perform basic initialization tasks. The primary constructor for a class is declared within the class header as follows:

class BankAccount (val accountNumber: Int, var accountBalance: Double) {
.
. 
    fun displayBalance()
    {
       println("Number $accountNumber")
       println("Current balance is $accountBalance")
    }
}Code language: Kotlin (kotlin)

Note that now both properties have been declared in the primary constructor, it is no longer necessary to also declare the variables within the body of the class. Since the account number will now not change after an instance of the class has been created, this property is declared as being immutable using the val keyword.

Although a class may only contain one primary constructor, Kotlin allows multiple secondary constructors to be declared in addition to the primary constructor. In the following class declaration the constructor that handles the account number and balance is declared as the primary constructor while the variation that also accepts the user’s last name is declared as a secondary constructor:

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

class BankAccount (val accountNumber: Int, var accountBalance: Double) {
  
    var lastName: String = ""
    
    constructor(accountNumber: Int, 
                accountBalance: Double,
                name: String ) : this(accountNumber, accountBalance) {
        
        lastName = name
    }
.
.
}Code language: Kotlin (kotlin)

In the above example, two key points need to be noted. First, since the lastName property is referenced by a secondary constructor, the variable is not handled automatically by the primary constructor and must be declared within the body of the class and initialized within the constructor.

var lastName: String = ""
.
.
lastName = nameCode language: Kotlin (kotlin)

Second, although the accountNumber and accountBalance properties are accepted as parameters to the secondary constructor, the variable declarations are still handled by the primary constructor and do not need to be declared. To associate the references to these properties in the secondary constructor with the primary constructor, however, they must be linked back to the primary constructor using the this keyword:

... this(accountNumber, accountBalance)...Code language: Kotlin (kotlin)

Initializer blocks

In addition to the primary and secondary constructors, a class may also contain initializer blocks which are called after the constructors. Since a primary constructor cannot contain any code, these methods are a particularly useful location for adding code to perform initialization tasks when an instance of the class is created. Initializer blocks are declared using the init keyword with the initialization code enclosed in braces:

class BankAccount (val accountNumber: Int, var accountBalance: Double) {
 
    init {
       // Initialization code goes here
    } 
.
.
}Code language: Kotlin (kotlin)

Calling methods and accessing properties

Now is probably a good time to recap what we have done so far in this chapter. We have now created a new Kotlin class named BankAccount. Within this new class, we declared primary and secondary constructors to accept and initialize account number, balance, and customer name properties. In the preceding sections, we also covered the steps necessary to create and initialize an instance of our new class. The next step is to learn how to call the instance methods and access the properties we built into our class. This is most easily achieved using dot notation.

Dot notation involves accessing a property, or calling a method by specifying a class instance followed by a dot followed in turn by the name of the property or method:

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

classInstance.propertyname
classInstance.methodname()Code language: Kotlin (kotlin)

For example, to get the current value of our accountBalance instance variable:

val balance1 = account1.accountBalanceCode language: Kotlin (kotlin)

Dot notation can also be used to set values of instance properties:

account1.accountBalance = 6789.98Code language: Kotlin (kotlin)

The same technique is used to call methods on a class instance. For example, to call the displayBalance method on an instance of the BankAccount class:

account1.displayBalance()Code language: Kotlin (kotlin)

Custom accessors

When accessing the accountBalance property in the previous section, the code is making use of property accessors that are provided automatically by Kotlin. In addition to these default accessors, it is also possible to implement custom accessors that allow calculations or other logic to be performed before the property is returned or set.

Custom accessors are implemented by creating getter and optional corresponding setter methods containing the code to perform any tasks before returning the property. Consider, for example, that the BankAcccount class might need an additional property to contain the current balance less any recent banking fees. Rather than use a standard accessor, it makes more sense to use a custom accessor that calculates this value on request. The modified BankAccount class might now read as follows:

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

class BankAccount (val accountNumber: Int, var accountBalance: Double) {
 
    val fees: Double = 25.00
    
    val balanceLessFees: Double 
        get() {
            return accountBalance - fees
        }
 
    fun displayBalance()
    {
       println("Number $accountNumber")
       println("Current balance is $accountBalance")
    }
}Code language: Kotlin (kotlin)

The above code adds a getter that returns a computed property based on the current balance minus a fee amount. An optional setter could also be declared in much the same way to set the balance value less fees:

val fees: Double = 25.00
 
var balanceLessFees: Double 
    get() {
        return accountBalance - fees
    }
    set(value) {
        accountBalance = value - fees
    }
.
.
}Code language: Kotlin (kotlin)

The new setter takes as a parameter a Double value from which it deducts the fee value before assigning the result to the current balance property. Even though these are custom accessors, they are accessed in the same way as stored properties using dot-notation. The following code gets the current balance less the fees value before setting the property to a new value:

val balance1 = account1.balanceLessFees
account1.balanceLessFees = 12123.12Code language: Kotlin (kotlin)

Nested and inner classes

Kotlin allows one class to be nested within another class. In the following code, for example, ClassB is nested inside ClassA:

class ClassA { 
    class ClassB {
    }
}Code language: Kotlin (kotlin)

In the above example, ClassB does not have access to any of the properties within the outer class. If access is required, the nested class must be declared using the inner directive. In the example below ClassB now has access to the myProperty variable belonging to ClassA:

class ClassA {
         var myProperty: Int = 10
    
    inner class ClassB {
               val result = 20 + myProperty  
    }
}Code language: Kotlin (kotlin)

Companion objects

A Kotlin class can also contain a companion object. A companion object contains methods and variables that are common to all instances of the class. In addition to being accessible via class instances, these properties are also accessible at the class level (in other words, without the need to create an instance of the class). The syntax for declaring a companion object within a class is as follows:

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

class ClassName: ParentClass {
   // Properties
   // Methods
 
   companion object {
        // properties
        // methods
    }
}Code language: Kotlin (kotlin)

To experience a companion object example in action, enter the following into the Kotlin online playground at https://try.kotl.in:

class MyClass {
   
    fun showCount() {
        println("counter = " + counter)
    }
    
    companion object {
        var counter = 1
        
        fun counterUp() {
            counter += 1
        }
    }
}
 
fun main(args: Array<String>) {
    println(MyClass.counter)
}Code language: Kotlin (kotlin)

The class contains a companion object consisting of a counter variable and a method to increment that variable. The class also contains a method to display the current counter value. The main() method simply displays the current value of the counter variable, but does so by calling the method on the class itself instead of a class instance:

println(MyClass.counter)Code language: Kotlin (kotlin)

Modify the main() method to also increment the counter, displaying the current value both before and after:

fun main(args: Array<String>) {
    println(MyClass.counter)
    MyClass.counterUp()
    println(MyClass.counter)
}Code language: Kotlin (kotlin)

Run the code and verify that the following output appears in the console:

1
2Code language: plaintext (plaintext)

Next, add some code to create an instance of MyClass before making a call to the showCount() method:

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

fun main(args: Array<String>) {
    println(MyClass.counter)
    MyClass.counterUp()
    println(MyClass.counter)
    
    val instanceA = MyClass()
    instanceA.showCount()
}Code language: Kotlin (kotlin)

When executed, the following output will appear in the console:

1
2
counter = 2Code language: plaintext (plaintext)

Clearly, the class has access to the variables and methods contained within the companion object.

Another useful aspect of companion objects is that all instances of the containing class see the same companion object, including current variable values. To see this in action, create a second instance of MyClass and call the showCount() method on that instance:

fun main(args: Array<String>) {
    println(MyClass.counter)
    MyClass.counterUp()
    println(MyClass.counter)
    
    val instanceA = MyClass()
    instanceA.showCount()
    
    val instanceB = MyClass()
    instanceB.showCount()
}Code language: Kotlin (kotlin)

When run, the code will produce the following console output:

1
2
counter = 2
counter = 2Code language: plaintext (plaintext)

Note that both instances return the incremented value of 2, showing that the two class instances are sharing the same companion object data.

 

You are reading a sample chapter from Jetpack Compose 1.5 Essentials.

Buy the full book now in Print or eBook format. Learn more.

Preview  Buy eBook  Buy Print

 

Summary

Object-oriented programming languages such as Kotlin encourage the creation of classes to promote code reuse and the encapsulation of data within class instances. This chapter has covered the basic concepts of classes and instances within Kotlin together with an overview of primary and secondary constructors, initializer blocks, properties, methods, companion objects, and custom accessors.


Categories