16. 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 explaining the concept as it relates to Kotlin application development.

16.1 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.

16.2 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.

16.3 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.

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

}

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 {

 

}

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

16.4 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

}

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.

16.5 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.

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")

    }

}

16.6 Declaring and Initializing a Class Instance

So far all we have done is define the blueprint for our class. In order 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()

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()

16.7 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

    }

.

.

}

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:

val account1: BankAccount = BankAccount(456456234, 342.98)

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

    }

.

.

}

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

val account1: BankAccount = BankAccount(456456234, 342.98, "Smith")

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")

    }

}

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:

class BankAccount (val accountNumber: Int, var accountBalance: Double) {

  

    var lastName: String = ""

    

    constructor(accountNumber: Int,

                accountBalance: Double,

                name: String ) : this(accountNumber, accountBalance) {

        

        lastName = name

    }

.

.

}

In the above example there are two key points which 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 = name

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)...

16.8 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

    }

.

.

}

16.9 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:

classInstance.propertyname

classInstance.methodname()

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

val balance1 = account1.accountBalance

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

account1.accountBalance = 6789.98

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()

16.10 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 which calculates this value on request. The modified BankAccount class might now read as follows:

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")

    }

}

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

    }

.

.

}

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. Regardless of the fact that 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.12

16.11 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 {

    }

}

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

    }

}

16.12 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:

class ClassName: ParentClass {

   // Properties

   // Methods

   companion object {

        // properties

        // methods

    }

}

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)

}

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)

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)

}

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

1

2

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

fun main(args: Array<String>) {

    println(MyClass.counter)

    MyClass.counterUp()

    println(MyClass.counter)

    

 val instanceA = MyClass()

    instanceA.showCount()

}

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

1

2

counter = 2

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()

}

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

1

2

counter = 2

counter = 2

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

16.13 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.235.46.191