Kotlin's OOP: A Comprehensive Guide

Kotlin's OOP: A Comprehensive Guide

If you have the basic knowledge of OOP then you are good to go with this article.

Classes

Class keyword is used to define classes. Class properties and member functions are declared like this. This class has no constructor so it calls the default constructor in the main function.

class Student{
    //properties
    var firstName = "Rafiul"
    var lastName = "Hasan"

    //member functions
    fun dance(){
        println("dancing")
    }

    fun play(){
        println("playing")
    }
}

fun main(){
    var student1 = Student()
    println(student1.firstName)
    println(student1.lastName)
    student1.dance()
    student1.play()
}

Constructors

in Kotlin the Constructor is the parenthesis after class name. It is also called the primary constructor.

class Student(fName:String, lName:String){
    //properties
    var firstName = fName
    var lastName = lName

    //member functions
    fun dance(){
        println("dancing")
    }

    fun play(){
        println("playing")
    }
}

fun main(){
    // input while creating instance
    var student1 = Student("Hasan", "Rafi")
    println(student1.firstName)
    println(student1.lastName)
    student1.dance()
    student1.play()
}

init{}

the code inside init's curly brackets will be executed whenever a new object will be created from this class.

class Student(fName:String, lName:String){
    //properties
    var firstName = fName
    var lastName = lName

    init{
        println("this line is printed whenever a new object is created")
    }
}

fun main(){
    var student1 = Student("Hasan", "Rafi")
}

Inheritance

Every class in Kotlin has a default parent class called "Any" means Any named a class which is the parent of all the classes in a program.

to inherit a class (Parent class) you have to make it open because by default classes can't be inherited they are private.

Here, Person is the parent class from which the Teacher class has inherited all the properties and methods. An object is created in teacher1 from Teacher class and tried to access properties like firstName and method like breathe() which are inherited and got the output successfully. In Teacher class header the fName and lName from the constructor is passed into the Person class's constructor to initialize the properties there.

open class Person(fName:String, lName:String){
    var firstName = fName
    var lastName = lName

    fun breathe(){
        println("breathing")
    }

    fun eat(){
        println("eating..")
    }

}

class Teacher(fName:String, lName:String) : Person(fName,lName){
    fun teach(){
        println("teaching...")
    }
}



fun main(){
    var teacher1 = Teacher("Abdul","Karim")
    println("${teacher1.firstName}")
    teacher1.breathe()
}

Overriding

Making a change in properties or in methods that are inherited from the parent class is called Overriding.

To override a method or a property in Kotlin you have to

  • put the open keyword before that method/ property in the parent class.

  • put override keyword before new method/property in the child class

open class Person(fName:String, lName:String){
    var firstName = fName
    var lastName = lName
    open var sunglass = "no"

    fun breath(){
        println("breathing")
    }

    open fun eat(){
        println("eating..")
    }

}

class ReachKid(fName:String,lName:String):Person(fName,lName){
    override var sunglass = "yes"
    override fun eat(){
        println("eating with spoon")
    }

}

fun main(){
    var person1 = ReachKid("Shane","Palsh")
    person1.eat()
    println("${person1.sunglass}")
}

super Keyword

Suppose in some cases you don't want to replace the whole method but you want to extend or add some more functionality to it. You can use super keyword here. So, in the code we added super.eat() in overridden method eat in ReachKid class. It will execute the parent method first which will print "eating" and then it will add extra feature like printing again "eating with spoon"

open class Person(fName:String, lName:String){
    var firstName = fName
    var lastName = lName

    fun breath(){
        println("breathing")
    }

    open fun eat(){
        println("eating..")
    }

}

class ReachKid(fName:String,lName:String):Person(fName,lName){
    override fun eat(){
        super.eat()
        println("eating with spoon")
    }

}

Abstract Class

some important features of abstract class

  • No instance can be created from an abstract class

  • No need to make an abstract class open they're by default open

  • An abstract class is like a blueprint for its subclasses.

  • By convention, methods are not built in abstract classes but they are mentioned to make aware the subclasses have their implementation of the class.

  • But that doesn't mean abstract classes can't have a method implementation. They can have.

In the following scenerio we don't need to make an object of an Animal we can create objects from Cat or Dog Classes. So, the must needed properties and methods one animal should have, are mentioned in the abstract class Animal. Abstract classes don't need to be opened cause they're open by default. Every animal can make noise but they vary from animal to animal. Every animal should have their way of making noise that's why the makeNoise() method is made abstract and it lets the subclasses make their own implementation of this method.

abstract class Animal(nameAnimal : String){
    var name : String = nameAnimal
    abstract fun makeNoise()
}

class Cat(name : String) : Animal(name) {
    override fun makeNoise(){
        println("meow meow")
    }
}

class Dog (name:String) : Animal(name){
    override fun makeNoise(){
        println("gheu gheu")
    }
}

Getters and Setters

We use getter and setter often without knowing it. Like for example we access property by className.propertyName here the dot notation works as a getter. Getter is used to get the value of a property of a class and the setter is used to set the value of a property of a class. Sometimes we need to control the access and setting of the value.

Here, we obtained email and age property by using . (dot) notation which works as a getter behind the scene.

class Entry(){
    var email = "RAFIUL@gmail.com"
    var age = 22

}
fun main(){
    var obj = Entry()
    println("${obj.email}")
    println("${obj.age}")
}

but if we want to get the email with all lowercase letters which is need to verify then we can use a custom getter using the get( ) method. This get() method automatically sets everything and when we will try to get the property using dot notation it will verify the logic inside the get() method. And similarly to set a variable we use a setter. In this example we don't want to set the age variable if the input is less than or equal to 18 so we can control the setting process using the set() method and add our logic to it.

class Entry(email:String, age:Int){
    var email = email
        get() {
           return field.lowercase()
        }
    var age = age
        set(value){
            field = if(value > 18) value else throw IllegalArgumentException("You're too young")
        }
}

fun main(){
    var obj = Entry("HASHNODEBLOG@GMAIL.COM", 22)
    println("${obj.email}")
    obj.age = 17
}

Encapsulation & Visibility Modifier

Four Visibility Modifiers in Kotlin

  • public : by default a method or property or class is public. And by public, it means that this method or property or class is available everywhere.

      open class A {
          public var a = "a"
      }
    
      class B:A(){
          var  z = a
      }
    
      fun main(){
          var obj = B()
          println("${obj.z}")
      }
    
      [Running] cd "d:\kt\" && kotlinc hello.kt -include-runtime -d hello.jar && java -jar hello.jar
      a
    
  • private: available for the corresponding class only. No outer class can access the method or property which is marked private.

      open class A {
          private var a = "a"
      }
    
      class B:A(){
          var  z = a
      }
    
      fun main(){
          var obj = B()
          println("${obj.z}")
    
      }
    
      [Running] cd "d:\kt\" && kotlinc hello.kt -include-runtime -d hello.jar && java -jar hello.jar
      hello.kt:79:14: error: cannot access 'a': it is invisible (private in a supertype) in 'B'
          var  z = a
    
  • protected : available for the corresponding class and subclasses only.

      open class A {
          protected var a = "a"
      }
    
      class B:A(){
          var  z = a
      }
    
      fun main(){
          var obj = B()
          println("${obj.z}")
    
      }
    
      [Running] cd "d:\kt\" && kotlinc hello.kt -include-runtime -d hello.jar && java -jar hello.jar
      a
    

internal : Inside the module (project) any file can access but no file outside of the module can access this.

Polymorphism

Polymorphism indicates the characteristic of presenting different forms of a particular method. This can be achieved in Kotlin by overloading.

here all methods are having same name but for having different parameter they are different from one another.

class Poly(){
    fun polyFun(){
        println("without parameter")
    }

    fun polyFun(x:String){
        println("with String parameter which is $x")
    }

    fun polyFun(x:Int){
        println("with integer parameter which is $x")
    }
}
fun main(){
    var obj = Poly()
    obj.polyFun()
    obj.polyFun("polymorphism")
    obj.polyFun(12)

}
[Running] cd "d:\kt\" && kotlinc hello.kt -include-runtime -d hello.jar && java -jar hello.jar
without parameter
with String parameter which is polymorphism
with integer parameter which is 12

I hope you enjoyed reading this article and found it useful. I would love to hear your feedback and suggestions for improvement. Please feel free to point out any errors or typos in the article.