2. Object Oriented Programming

Kotlin is an object oriented language, it allows us to create classes and instances of these classes – objects. Just like in any object oriented the principles apply here as well – Inheritance, Polymorphism, Abstraction and Encapsulation. There is a small difference however, by default everything is public in Kotlin so if you want to “hide” a property, we have to explicitly say so.

Content:

2.1 Interfaces

We declare interfaces and classes in a similar fashion as in Java. We use the interface keyword, give the interface a name and we have the interface body in which we declare our methods.

interface Animal { //1
 fun speak() //2,3,4
}

1 – as we already mentioned we don’t need to say the interface should be public. It’s public by default.
2 – we use the fun keyword to declare the method
3 – the method returns nothing
4 – the method doesn’t take any parameters

For classes we have:

class Human : Animal { // 1,2
  override fun speak () = println("Hello") // 3,4
}

1 – we use “:” to specify both extension and implementation. Again less typing, less ceremony, very much like C# in this case.
2 – again class is public by default same as with interfaces.
3 – to implement the method in the class we use the override keyword
4 – Kotlin has something called method expressions. If our method is simply one line we can use “=” and specify the body right after it.

Now, we want to create an object of this class:

fun main(args: Array<String>) {
val p = Human() // 1
p.speak() // 2 }

1 – we are creating a new instance of type Human, notice there is no new keyword
2 – this will print for us “Hello”

2.2 Classes

We already know that classes in Kotlin are by default public but we haven’t mentioned yet that they are also always final. What this means is that we can’t derive from them. Lets say we want to extend our class Human and create a new class Teenager:

class Teenager() : Human()

The compiler will not allow this, not because teenagers aren’t humans, but because the Human type is final and cannot be inherited from. To fix this we need to make the class Human “open“:

open class Human // 1

1 – open keyword is used to specify that derivation is allowed. Therefore, our class can now be extended. This way Kotlin makes sure that “opening” the class was a design choice, we have analyzed the code and we said, yes, we need to derive from this class.

2.2.a Constructors

Another thing in Kotlin is that we can define a class with a constructor in-line. Like this:

class Human(val name: String)

This is the same class Human as before but now it has a constructor. This constructor takes a single value of type String which is stored inside the class as a property called “name“. This single parameter constructor is now our primary constructor, there is no longer a default one, the primary constructor will be the go to constructor. To create an instance of this class we need to call the constructor and pass it a string value:

val ivo = Human("Ivo") // 1

1 – again let’s emphasis there is no new keyword

Now let’s change the speak method a bit to make use of our new structure:

class Human(val name: String) : Animal { // 1,2
  override fun speak () = println("Hello, my name is $name") // 3,4
}

fun main(args: Array<String>) {
  val ivo = Human("Ivo") // 1
  ivo.speak() // 2
}

This will print for us “Hello, my name is Ivo”.

Next, let’s imagine that we need to implement some logic when the object is constructed and simply creating the properties isn’t enough for us. To do this and keeping the same syntax, we can fire a method called init(). The init block runs right after the object has been constructed. We would normally use it to do more complex initialization, for example, logging, creating database connections or something similar. To give an example, let’s change a bit more our Human class:

open class Human(val name: String, var age: Int) { // 1, 2
  init() {
    if(age<16 && name == "Toni") throw Exception("You are too young for this")// 3, 4*
  }
  override fun speak()=println("Hello, my name is $name and I'm $age years old") // 5
}

fun main(args: Array<String>) {
 val ivo = Human("Ivo", 25)
 val toni = Human("Toni", 14)
 ivo.speak()
 toni.speak()
}

1 – we used the open keyword to make the class extendable
2 – we added a variable age of type integer
3 – simple number check that throws exception if human is younger than 16
4* – notice how Kotlin allows us to compare strings with “==” operator. In Java you must use the equals() or equalsIgnoreCase() method to achieve this
5 – we now print the age as well as the name

The result of running that code will be an exception saying “You are too young for this” when we try to initialize the toni object.

Kotlin also allows us to create secondary constructors. For that we need to use the constructor keyword. Here is an example:

open class Human(val name: String, var age: Int) {
var isMarried = false // 1

constructor(name: String, age: Int, isMarried: Boolean) : this(name, age){//2, 3
this.isMarried = isMarried
}

override fun speak()=println("Hello, my name is $name and I'm $age years old")
}

1 – we create new variable isMarried. We are taking advantage of Kotlin’s type inference by not explicitly specifying a type.
2 – we use the constructor keyword
3 – we use the this keyword to call the primary constructor

While we can have multiple constructors that is not the preferred approach. Kotlin has another feature called default parameter values. This is the preferred approach than the secondary constructors in Kotlin. What happens is we give a default value of a parameter in the primary constructor:

open class Human(val name: String, var age: Int, var isMarried : Boolean = false) {
override fun speak()=println("Hello, my name is $name and I'm $age years old")
}

We should use secondary constructors only if we are deriving from a class that requires it.

2.3 Companion Object

In our introduction chapter we already mentioned that there are no statics in Kotlin. So what do we do if want to achieve the same behavior as statics give us? – Kotlin provides us with a tool called Companion Object. For example if we want to run the main method from our Human class we need to do two things: wrap the main method in a companion object and annotate the method with @JvmStatic. Here is how it would look like:

open class Human(val name: String, var age: Int, var isMarried : Boolean = false) {

companion object {
@JvmStatic
fun main(args: Array<String>) {
val ivo = Human("Ivo", 25)
val toni = Human("Toni", 14)
ivo.speak()
toni.speak()
}
}
override fun speak()=println("Hello, my name is $name and I'm $age years old")
}

2.4 Data Classes

Very often, we need to create classes that we will use to hold data in them, Data Transfer Object(DTO) classes would be a perfect example. Normally, they would also provide the same set of functionality – comparing instances of these classes for example. So Kotlin helps us with this by using something called data class. We simply mark the class definition as data with the data keyword and Kotlin will the provide us with equals, hashCode, toString and other methods. Let’s take a look at an example:

data class User(val name : String, val id : Int)

fun main(args: Array<String>) {
val ivo = User("Ivo", 25)
println(ivo) // 1

val (name, id) = ivo // 2
println("User is $name with id $id")

val otherUser = ivo.copy(id=3) // 3
println(otherUser) // 1
}

1 – this will call the toString() method of User and it will print for us “User(name=Ivo, id=25)” instead of the object reference
2 – Kotlin offers deconstruction. The following line will print “User is Ivo with id 25”. This happens thanks to methods in the data class called componentN() where N is the order number of the property. So ivo.component1() will return the name in our case and ivo.component2() will get us the id
3 – we can copy an object’s value from it’s parameters by calling the copy() method. As a parameter we can pass the properties that we don’t want to copy and assign it a desired new value. In our example otherUser will be printed as “User(name=Ivo, id=3)”

<< 1. Introduction                                                         3. Functional Programming >>