Object-Oriented Programming in Kotlin

Introduction

This article demonstrates how object-oriented programming works in Kotlin. You might be able to skip some parts based on your level of familiarity. It will be an advantage if you have prior practical experience with an object-oriented language. You will learn how to program in Kotlin with regard to classes and objects. However, Kotlin has some significant differences that help you write less code. You also study interface delegation and abstract classes.

This article is part of the Kotlin Learning Series for programmers. You'll get the most value out of this series if you go through the articles in sequence.

Previous Articles

About Object-oriented programming

Object-Oriented Programming (OOP) is a programming paradigm that focuses on the creation of objects that have certain properties and behaviors. Kotlin is a modern programming language that fully supports OOP concepts, making it easy to create flexible, scalable, and maintainable code.

Kotlin

Both object-oriented programming (OOP) and functional programming are supported by Kotlin. Real-time objects and classes serve as the foundation for object-oriented programming. Additionally, Kotlin supports OOP language pillars like polymorphism, inheritance, and encapsulation.

Class in Kotlin

In Kotlin, a class is a blueprint for creating objects. A car is the like a blueprint for making the different models of car objects.

Objects in Kotlin

An object is an instance of a class that has its own set of data and behavior.

Constructors and initializer blocks in Kotlin

A class in Kotlin can have a primary constructor and one or more secondary constructors. The primary constructor is a part of the class header, while the secondary constructors are prefixed with the constructor.

Primary and Secondary Constructor

Class Properties in Kotlin

A property is a variable declared in a class, and a function is a block of code that performs a specific task.

Class Member Functions or Methods in Kotlin

Methods, also called member functions, are the functionality of the class. Methods are what you can "do" with the object. For example, you can start_engine() on a Car object.

Interface in Kotlin

An interface is a specification that a class can implement. For example, Blowing horn is common to objects other than a car, and blow horn generally happens similarly to different objects like bikes. So you could have an interface called BlowHorn that defines a blow_horn() method. The Car class could implement the blow_horn interface to use the horn while driving.

Package in Kotlin

A code library can be created by grouping related pieces of code into packages to keep it organized. Once a package is built, you can reuse the code and classes in it by importing the package's contents into another project.

Classes and objects

To create a class in Kotlin, you use the keyword "class" followed by the class name and the class body. You can create an object from a class using the "new" keyword, but in Kotlin, you can also create objects using the "object" keyword, which creates a singleton object.

Class and Object

Let's Create a Car Class

Open IntelliJ IDEA and open a project (if not there, create it); in the project pane right, click on the src folder and select New > Package and name it myProject. Now create a new Kotlin File / Class and name the class Car. Inside the Car class, define the var property like below.

class Car() {
    var Car_Model: String = "Tesla"
    var Car_type: String = "Electric"
    var Car_details: String = "run 450 on single charge"
    var Car_id: Int = 5123

}

We can access the class property with the help of its instance, for example, car.car_id.

Note. If you declared these properties  val instead of var, the properties would be immutable. You could only set them once, and all the instances Car would have the same dimensions.

Also note that IntelliJ IDEA underlines the name of each var in your code, but not each val. Kotlin's coding style prefers immutable data when possible, so IntelliJ IDEA draws your attention to mutable data so you can minimize its use.

Create a main() function

To create a new file called main.kt to hold the main() function. Under the Kind dropdown, keep the selection as File, and name the file main.kt. IntelliJ IDEA includes the package name but doesn't include a class definition for a file. Define a buildCar() function and inside, create an instance of Car. To create an instance, reference the class as if it were a function, Car(). This calls the class's constructor and creates an instance of the Car class, similar to use new in other languages. Define a main() function and call buildCar().

fun buildCar() {
    val myCar = Car()
}

fun main() {
    buildCar()
}

Add a Method to class

In the Car, the class adds a method to print all the properties that the class has.

    fun printCarDetails() {
        println(
            "Car id : $Car_id" +
                    "\nCar Model : $Car_Model" +
                    "\nCar Type : $Car_type" +
                    "\nCar Details : $Car_details",
        )
    }

In main.kt, in buildCar(), call the printCarDetails() method on myCar object.

fun buildCar() {
    val myCar = Car()
    myCar.printCarDetails()
}

Run the program and notice the output.

Output

In buildCar(), add code to set the Car details to "After upgradation now the car runs 550 on a single charge" and print the changed dimension properties.

fun buildCar() {
    val myCar = Car()
    myCar.Car_details = "After upgradation now the car runs 550 on single charge"
    myCar.printCarDetails()
}

Run your program and observe the output.

Output

Add a constructor

Previously we had the same dimensions for each instance of the Car; in reality, each car has different types of dimensions. To achieve this, we use a constructor. We can change the dimensions once it is created by setting the properties, but it would be simpler to create the correct size to begin with.

In some programming languages, the constructor is defined by creating a method within the class that has the same name as the class. In Kotlin, you define the constructor directly in the class declaration, specifying the parameters inside parentheses as if the class was a method. As with functions in Kotlin, those parameters can include default values.

The Car class include four constructor parameter with default values and assigns them to the corresponding properties.

class Car(carmodel: String = "Base Model", cartype: String = "Petrol", cardetails: String = "This is just a car frame", carid: Int = 0) {
    var Car_Model: String = carmodel
    var Car_type: String = cartype
    var Car_details: String = cardetails
    var Car_id: Int = carid
}

 To reduce the size of the code, we can define the properties directly with the constructor, using var or val, and Kotlin also creates the getters and setters automatically. Then you can remove the property definitions in the body of the class.

class Car(var carmodel: String = "Base Model", var cartype: String = "Petrol", var cardetails: String = "This is just a car frame", var carid: Int = 0) {

}

Now to create an instance of the Car class with the constructor, we have to specify some arguments to give the instance its specific properties. Let's try to create some of them. See the below examples.

fun buildCar() {
    val myCar = Car("Tesla", "Electric", "run 450 on single charge", 5123)
    println("\t Car 1 details")
    myCar.printCarDetails()

    val myCar2 = Car(carmodel = "Maruti Sizuki 200", cartype = "petrol")
    println("\t Car 2 details")
    myCar2.printCarDetails()

    val myCar3 = Car("Defender")
    println("\t Car 3 details")
    myCar3.printCarDetails()

    val myCar4 = Car()
    println("\t Car 4 details")
    myCar4.printCarDetails()
}

Run the program and notice the output.

Output

Kotlin takes care of what is needed from the default value and named parameters.

Add init blocks 

Sometimes we could have to perform some specific tasks on initialization of the class; we can achieve functionality through the init block. The init block can be used to initialization of more code if needed. Let's add the init block to the Car class that will ready the car by starting the engine.

class Car(var carmodel: String = "Base Model", var cartype: String = "Petrol", var cardetails: String = "This is just a car frame", var carid: Int = 0) {
    init {
        println("\n $carmodel engine started ")
    }
}

Run the program and notice the output.

 Tesla engine started 
	 Car 1 details
Car id : Tesla
Car Model : Tesla
Car Type : Electric
Car Details : run 450 on single charge

Notice that the init blocks are executed in the order in which they appear in the class definition, and all of them are executed when the constructor is called.

Add secondary constructor

Here, you learn about secondary constructors and add one to your class. In addition to a primary constructor, which can have one or more init blocks, a Kotlin class can also have one or more secondary constructors to allow constructor overloading, that is, constructors with different arguments.

Let's add a secondary constructor in the Car class that takes a number of people it can afford inside the car. 

    constructor(numberOfPeople: Int) : this(){
        // without driver capacity
        val passenger_capacity = numberOfPeople -1
        cardetails = cardetails.plus(" sitting capacity is $passenger_capacity")
    }

In the buildCar() function, add a call to create a Car using your new secondary constructor. Print the car details with passenger capacity.

Run the program and notice the output.

We can include the constructor keyword in the primary constructor, too, but it's not necessary in most cases.

Add a property getter

Let's add a getter here. Kotlin automatically defines getters and setters when you define properties, but sometimes the value for a property needs to be adjusted or calculated. For example, above, you printed the sitting capacity of the car. You can make the car capacity available as a property by defining a variable and a getter for it because sometime capacity of passenger can be less and of the greater need to return the calculated value, which you can do with a one-line function.

    val passenger_capacity : Int
        get() = if (cartype.equals("Electric")) 7 else 8
    init {
        println("\n capacity of this car is $passenger_capacity")
    }

Remove the constructor and print the value.

fun buildCar() {
    val myCar4 = Car()
    println("\t Car 1 details")
    myCar4.printCarDetails()
    myCar4.passenger_capacity
}

Run the program and notice the output.

Output

Add a property setter

It's time to add a setter to our Car class.

In the Car class, change passenger_capacity to a var so it can be set more than once. Add a setter for the passenger_capacity property by adding a set() method below the getter. Set the value of the cartype to the value given.

    var passenger_capacity : Int
        get() = if (cartype.equals("Electric")) 7 else 8
        set(value){
            cartype = "Electric"
        }

In buildCar(), add code to set the passenger_capacity to 7 only and print the new passenger_capacity.

fun buildCar() {
    val myCar4 = Car()
    println("\t Car 1 details")
    myCar4.printCarDetails()
    myCar4.passenger_capacity = 7
    myCar4.printCarDetails()
}

Run the program and notice the output.

Output

Visibility modifiers

Kotlin uses visibility modifiers to regulate how visible a class, function, or property is. By default, classes, methods, properties, and member variables can all be accessed from anywhere because everything in Kotlin is public. Kotlin offers the following four visibility modifiers:

Modifiers in Kotlin

  1. Public: Public refers to being observable outside of the classroom. The class's methods and variables are all, by default, made public. This is the default visibility modifier in Kotlin, which means that the class, function, or property is visible to all other parts of the program.
  2. Private: When something is private, it can only be seen by members of that class (or source file if you're working with functions). 
  3. Protected: Similar to private, protected is also accessible to any subclasses. The protected the modifier is not available for top-level declarations.
  4. Internal: It will only be visible inside that module if it is internal. A module is made up of several Kotlin files that have been compiled together, such as a library or application, for example, An IntelliJ IDEA module, A Maven project, or A Gradle source set (with the exception that the test source set can access the internal declarations of main), A set of files compiled with one invocation of the <kotlinc> Ant task. 

Example

open class Outer {
    private val a = 1
    protected open val b = 2
    internal open val c = 3
    val d = 4  // public by default

    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a is not visible
    // b, c and d are visible
    // Nested and e are visible

    override val b = 5   // 'b' is protected
    override val c = 7   // 'c' is internal
}

class Unrelated(o: Outer) {
    // o.a, o.b are not visible
    // o.c and o.d are visible (same module)
    // Outer.Nested is not visible, and Nested::e is not visible either
}

Visibility modifiers can be applied to classes, objects, interfaces, constructors, functions, properties, and their setters. Getters are always as visible as their corresponding properties. Local variables, functions, and classes can't have visibility modifiers.

Subclasses and inheritance

In Kotlin, inheritance, and subclasses are effective tools for classifying and reusing code. They give programmers the freedom to create classes with shared functionality and the ability to customize that functionality in subclasses. Developers can create code that is modular, extendable, and maintainable by using inheritance and subclasses wisely.

Subclass and Inheritance

In Kotlin, by default, classes cannot be subclassed. Similarly, properties and member variables cannot be overridden by subclasses (though they can be accessed).

You must mark a class as open to allow it to be subclassed. Similarly, you must mark properties and member variables as open in order to override them in the subclass. The open keyword is required to prevent accidentally leaking implementation details as part of the class's interface.

Get ready for subclasses 

open class Car(open var carmodel: String = "Base Model",open  var cartype: String = "Petrol",open var cardetails: String = "This is just a car frame",open var carid: Int = 0) {

    open var passenger_capacity : Int
        get() = if (cartype.equals("Electric")) 7 else 8
        set(value){
            cartype = "Electric"
        }
}

Add another property airBagsAvailability, assign it with the value true, and add wholeCarCapacity, which will return the total capacity of the car with the driver.


    open val airBagsAvailability = false

    open var wholeCarCapacity : Int = 0
        get() = passenger_capacity +1

In the printDetails(), add the following code to print the properties of the car.

    fun printCarDetails() {
        println(
            "Car id : $carmodel" +
                    "\nCar Model : $carmodel" +
                    "\nCar Type : $cartype" +
                    "\nCar Details : $cardetails",
        )
//        println("capacity of car is  $passenger_capacity")
        println("Air bag Avaialbility in Car "+airBagsAvailability)
        println("The total capacity of car is "+wholeCarCapacity)

    }

In the buildCar() method, call the printDetails() method like below.

fun buildCar() {
    val myCar4 = Car()
    println("\t Car 1 details")
    myCar4.printCarDetails()
}

Run the program and notice the output.

Output

Implementing subclass

To create a subclass of Car called Marcedes Benz S, which has only seater capacity and is a luxury car. Airbags are available in this car class. So to customize these details, we will override some of its properties like below.

class mercedes_Benz_S(override var cardetails: String, override var carid: Int): Car(cardetails= cardetails, carid = carid){
}

Override passenger_capacity to set the car passenger capacity to just 4, while airbags will be available and the whole car capacity will be passenger capacity +1 like below-

    override var passenger_capacity: Int
        get() = 4
        set(value) {
            passenger_capacity = value
        }
    override val airBagsAvailability = true

    override var wholeCarCapacity: Int
        get() = passenger_capacity+1
        set(value) {}

In the BuildCar(). create an instance of the Mercedes class and call the print details method on it.

fun buildCar() {

    val myCar4 = Car()
    println("\t Frame of car details")
    myCar4.printCarDetails()

    val mymercedes_Benz_S = mercedes_Benz_S(cardetails = "This is mercedes known for luxery", carid = 6420)
    println("\t Mercedes Car details")
    mymercedes_Benz_S.printCarDetails()

}

Run the program and notice the output-

Output

The colon symbol and the superclass's name are used to create a subclass of a class, as we saw above. Subclasses inherit the properties and functions of the superclass and can also override them if necessary. You can also create an abstract class that cannot be instantiated directly but can be used as a superclass for other classes.

Summary

In Kotlin, OOPs principles are used to organize software design around objects. Classes are blueprints for creating objects, and objects are instances of classes with their data and behaviour. Visibility modifiers control the visibility of classes, functions, and properties, and subclasses can be created using inheritance. I hope this has been a helpful guide for you; it will be helpful for me if you write your genuine view about this article in the comments. Thank you!

Questions

Some possible questions related to OOP in Kotlin include,

Questions

Q. What are the advantages of using OOP in Kotlin?

Q. How do you define a class in Kotlin?

Q. What is the difference between a class and an object in Kotlin?

Q. What are visibility modifiers, and how do you use them in Kotlin?

Q. How do you create a subclass in Kotlin?


Similar Articles