Weekend with Kotlin: Everything you need to know to get started with Kotlin — Part III of III

Faheem un nisa
BYJU’S Exam Prep Engineering
7 min readApr 2, 2019

This article is based on the assumption that you have read and understood the first two parts of this series, viz

Let’s get started!

…10. Classes & Constructors:

A class is declared using the keyword class.

Primary Constructor: The primary constructor is part of the class header

class Customer public @Inject constructor(name: String) { ... }class Person constructor(firstName: String) { ... }

If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted:

class Person(firstName: String) { ... }

The primary constructor cannot contain any code. Initialisation code can be placed in initializer blocks, which are prefixed with the init keyword.

class InitOrderDemo(name: String) {

init {
println(“First initializer block that prints ${name}”)
}
}

Java v/s Kotlin: Face-off

Java:

public class FaceOff{String name = ”java”;int age = 20;public FaceOff(String name, int age){this.name = name;this.age= age;}}

Kotlin:

public class FaceOff(var name:String=”kotlin”, var age:Int=3){}

Secondary constructors: The class can also declare secondary constructors, which are prefixed with constructor:

class Person {constructor(parent: Person) {parent.children.add(this)}}

If the class has a primary constructor, each secondary constructor needs to delegate to the primary constructor using the this keyword:

class Ball(var name: String, var length: Int, var rad: Int?) {var shape: String = ""constructor(name:String, length, rad: Int?, shape: String) : this(name, length, radius) {this.shape = shape}}

Code in initializer blocks effectively becomes part of the primary constructor. Delegation to the primary constructor happens as the first statement of a secondary constructor, so the code in all initializer blocks is executed before the secondary constructor body. Even if the class has no primary constructor, the delegation still happens implicitly, and the initializer blocks are still executed.

If all of the parameters of the primary constructor have default values, the compiler will generate an additional parameterless constructor which will use the default values. This makes it easier to use Kotlin with libraries such as Jackson or JPA that create class instances through parameterless constructors.

11. Overriding

Overriding methods

Unlike Java, Kotlin requires explicit modifiers for overridable members (we call them open) and for overrides:

open class Base {open fun v() { ... }fun nv() { ... }}class Derived() : Base() {override fun v() { ... }}

The override modifier is required for Derived.v(). If it were missing, the compiler would complain.

-If there is no open modifier on a function, like Base.nv(), declaring a method with the same signature in a subclass is illegal, either with override or without it.

-The open modifier has no effect when added on members of a final class (i.e.. a class with no open modifier).

-A member marked override is itself open, i.e. it may be overridden in subclasses. If you want to prohibit re-overriding, use final:

open class AnotherDerived() : Base() {final override fun v() { ... }}

Overriding Properties

Overriding properties works in a similar way to overriding methods; properties declared on a superclass that are then redeclared on a derived class must be prefaced with override, and they must have a compatible type. Each declared property can be overridden by a property with an initialiser or by a property with a getter method.

open class Foo {open val x: Int get() { ... }}class Bar1 : Foo() {override val x: Int = ...}

You can also override a val property with a var property, but not vice versa. This is allowed because a val property essentially declares a getter method, and overriding it as a var additionally declares a setter method in the derived class.

Derived class initialisation order

During construction of a new instance of a derived class, the base class initialisation is done as the first step (preceded only by evaluation of the arguments for the base class constructor) and thus happens before the initialisation logic of the derived class is run.

open class Base(val name: String) {init { println(“Initializing Base”) }open val size: Int = 
name.length.also {
println(“Initializing size in Base: $it”) }
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also {
println(“Argument for Base: $it”) }) {
init {
println(“Initializing Derived”)
}
override val size: Int =
(super.size + lastName.length).also { println(“Initializing size in Derived: $it”) }
}

Input: “hello”, “world”

Output:

Argument for Base: HelloInitializing BaseInitializing size in Base: 5Initializing DerivedInitializing size in Derived: 10

It means that, by the time of the base class constructor execution, the properties declared or overridden in the derived class are not yet initialized. If any of those properties are used in the base class initialization logic (either directly or indirectly, through another overridden open member implementation), it may lead to incorrect behavior or a runtime failure. When designing a base class, you should therefore avoid using open members in the constructors, property initializers, and init blocks.

Calling the superclass implementation

Code in a derived class can call its superclass functions and property accessors implementations using the super keyword:

open class Foo {
open fun f() { println(“Foo.f()”) }
open val x: Int get() = 1
}
class Bar : Foo() {
override fun f() {
super.f()
println(“Bar.f()”)
}

override val x: Int get() = super.x + 1
}

Inside an inner class, accessing the superclass of the outer class is done with the super keyword qualified with the outer class name: super@Outer:

class Bar : Foo() {override fun f() { /* ... */ }override val x: Int get() = 0inner class Baz {fun g() {super@Bar.f() // Calls Foo's implementation of f()println(super@Bar.x) // Uses Foo's implementation of x's getter}}}

Overriding Rules

In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits many implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones). To denote the supertype from which the inherited implementation is taken, we use super qualified by the supertype name in angle brackets, e.g. super<Base>:

open class A {
open fun f() { print(“A”) }
fun a() { print(“a”) }
}
interface B {
fun f() { print(“B”) } // interface members are ‘open’ by default
fun b() { print(“b”) }
}
class C() : A(), B {
// The compiler requires f() to be overridden:
override fun f() {
super<A>.f() // call to A.f()
super<B>.f() // call to B.f()
}
}

It’s fine to inherit from both A and B, and we have no problems with a() and b() since C inherits only one implementation of each of these functions. But for f() we have two implementations inherited by C, and thus we have to override f() in C and provide our own implementation that eliminates the ambiguity.

12. Abstract Classes

A class and some of its members may be declared abstract. An abstract member does not have an implementation in its class. Note that we do not need to annotate an abstract class or function with open — it goes without saying.

13. Coroutines

One can think of a coroutine as a light-weight thread: a light weight thread means it doesn’t map on native thread and so it doesn’t require context switching on processor, and that’s why it is faster. Like threads, coroutines can run in parallel, wait for each other and communicate. The biggest difference is that coroutines are very cheap, almost free: we can create thousands of them, and pay very little in terms of performance. True threads, on the other hand, are expensive to start and keep around.

Coroutines are available in many languages and are two types:

  • Stackless
  • Stackful

Kotlin implements stackless coroutines — it’s mean that the coroutines don’t have own stack, so they don’t map on native thread.

Coroutines and the threads both are multitasking. But the difference is that threads are managed by the OS and coroutines by the users.

There are the two functions to start the coroutine:

  • launch{}
  • async{}

The difference is that the launch{} does not return anything and the async{} returns an instance of Deferred<T>, which has an await()function that returns the result of the coroutine like we have future in Java. and we do future.get() in Java to the get the result.

Launch:

fun main(args: Array<String>) {
println("Kotlin Start")
GlobalScope.launch() {
delay(2000)
println("Kotlin Inside")
}
println("Kotlin End")
Thread.sleep(3000)
}
//output//Kotlin Start
//Kotlin End
//Kotlin Inside

This starts a new coroutine on a given thread pool.

Threads still exist in a program based on coroutines, but one thread can run many coroutines, so there’s no need for too many threads.

If you directly call delay in the main function like

fun main(args: Array<String>) { delay(2000) }

you get the following error:

Suspend functions are only allowed to be called from a coroutine or another suspend function.

Here, the delay function is suspend function, so we can only call it from a coroutine or an another suspend function.

To correct it:

fun main(args: Array<String>) {
runBlocking {
delay(2000)
}
}

Let’s see one more example

suspend fun doWorkFor1Seconds(): String {
delay(1000)
return “doWorkFor1Seconds”
}
suspend fun doWorkFor2Seconds(): String {
delay(2000)
return “doWorkFor2Seconds”
}

Launch:

// Serial execution 
private fun doWorksInSeries() {
launch(CommonPool) {
val one = doWorkFor1Seconds()
val two = doWorkFor2Seconds()
println("Kotlin One : " + one)
println("Kotlin Two : " + two)
}
}
// The output is
// Kotlin One : doWorkFor1Seconds
// Kotlin Two : doWorkFor2Seconds

Async:

// Parallel execution
private fun doWorksInParallel() {
val one = async(CommonPool) {
doWorkFor1Seconds()
}
val two = async(CommonPool) {
doWorkFor2Seconds()
}
launch(CommonPool) {
val combined = one.await() + "_" + two.await()
println("Kotlin Combined : " + combined)
}
}
// The output is
// Kotlin Combined : doWorkFor1Seconds_doWorkFor2Seconds

Async is used here, so that we can use await() to get the result.

That’s it! You are ready for the real world now! Time to get your hands dirty with Kotlin!

Adios, amigos!

--

--