What makes Kotlin different?

Photo by Louis Tsai on Unsplash

What makes Kotlin different?

A Kotlin guide on what Kotlin makes different

Kotlin is a very popular programming language in the mobile application development world, but it is a bit of a new experience for backend developers.
Kotlin is designed to interoperate fully with Java and the JVM version of its standard library depends on the Java Class Library, but type inference allows its syntax to be more concise. Kotlin mainly targets the JVM but also compiles JavaScript or native code.

In this article, I want to show some fundamentals of Kotlin, and I will try to compare it with other programming languages.

Variable Initialize

// Java
Person p = new Person("mehmet");

// Kotlin
val p1 = Person("mehmet")
p1 = Person("sezer") // Produce error, val is like final variable 

var p2: Person = Person("test")
p2 = Person("test2") // No error

As it can be seen, In Kotlin variables can be defined by using val and var keywords.

Variables produced using the var keyword are known as mutable variables, and they can be assigned multiple times.

Variables produced using the val keyword are like the ones produced using final keyword in Java, and they are known as immutables. They can be initialized only a single time.

Also, type can be given to variables manually but it’s optional, Kotlin can understand the types automatically.

And maybe you noticed that in Kotlin you don’t need to use “;” at the end of each line :)

Data Class

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

In Kotlin, there is a data keyword, which overrides toString, hashCode, equals automatically.

Data classes have a copy method, by using that a new object from the existing one can be created while changing some fields.

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

val user = User("mehmet", 23)
val newUser = user.copy(name = "sezer")
println(newUser) // prints User(name=sezer, age=23)

Here, the primary constructor in Kotlin which is a little bit different from Java can be seen.

// Java
class User{
    String name;
    int age;      

    public User(String name, int age){
        this.name = name;
        this.age = age;  
    }
}

// Kotlin
class User(val name: String, val age: Int) // Class definition with primary constructor

val user1 = User("name", 10) // Object creation
val user2 = User(age = 10, name = "name") // named constructor call

Constructors or methods can be called without any order by using named calls. I prefer to use named calls for every method and constructor call as it prevents developers from making mistakes.

Null Safety

Getting a null reference exception while accessing a null referenced variable is one of the most common dangers in programming languages. Java raises NullPointerException analogs to null reference exception.

But in Kotlin these exceptions can be prevented, and the code becomes safer. In Kotlin variables are non-null by default.

var abc: String = "abc" // Regular initialization means non-null by default
abc = null // compilation error

Still, if you want to make “abc” nullable you need to add “?” to the end of the field type.

var abc: String? = "abc" // Nullable variable
abc = null // no error

In Kotlin we have safe calls, which is a great feature for accessing nullable properties.

// Java
String ex = null
if(ex != null){
    System.out.println(ex.length);
}

// Kotlin
val a: String? = null
val b: String? = "test"
println(b?.length) // prints 4
println(a?.length) // prints null because a is null

Let’s say you want to learn some user’s school department name, and every field is nullable.

println(user?.school?.department?.name)
// prints null if user or school or department is null
// prints whatever name field if user, school, department is not null

And lastly, Kotlin has Elvis operator to handle default values for null expression.

val b = null
val l = b?.length ?: -1 // returns -1

Coroutine ❤

It may be the biggest reason I love Kotlin. As you know writing asynchronous or non-blocking code is a little bit painful according to synchronous programming. Before speaking coroutines let me tell you some other techniques and solutions.
1- Threading: To achieve more scale you can create threads, which may avoid applications from blocking. However, there are some drawbacks:

  • Creating a new operating system thread is a very expensive operation.
  • Threads require context switches which are costly too.
  • There are limited threads that we can use.
  • Debugging and avoiding race conditions in thread techniques are a little bit painful.

2- Callbacks: When callbacks are used, a function can be passed as a parameter to another function, and the function which passed as the parameter is invoked when the process has been done. Drawbacks:

  • Callback hell.
  • Error handling is complicated.

3- Futures, promises, and others: Futures or promises assure that they will return a usable object named Promise, at a later time. Drawbacks:

  • You need to change your code style.
  • Error handling still can be complicated.

4- Reactive: Reactive hopes to achieve a state where the data is observable and in an infinite amount, in other words, observable streams. Drawbacks:

  • Writing reactive code is very hard, you need to change your thinking and coding style to write reactive code.

Kotlin’s approach to working with asynchronous code is using coroutines, which is the idea of suspendable computations. The function is suspended on a line and continues to run after a time or via a system event.

The Kotlin team defines coroutines as “lightweight threads”. But I think it is more than that, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one. You can control the threads that coroutines use.

According to my experience, Kotlin coroutines are similar to Go goroutines.

One of the benefits of coroutines is that when it comes to the developer, writing non-blocking code is essentially the same as writing blocking code.

There are mainly two functions in Kotlin to start the coroutines.

  • launch{ } which is fire and forget does not return anything.
  • async{ } which can return. You can await the response.

In Kotlin main function starts with the main coroutine.

launch { // launch a new coroutine and continue
    delay(1000L) // non-blocking delay for 1 second the coroutine will suspend here
    println("World!") // print after delay -> this line can run different thread(it depends)
}
println("Hello") // main coroutine continues while a previous one is delayed

// Hello
// World!

data class User(val name: String)

val user = async { // launch a new coroutine and continue
    delay(1000L) // database simulation, thread will not be blocked here.
    User("Mehmet") // return database record
}.await()

println(user) // prints User(name = "Mehmet")

In line 13, the current thread will not be blocked, this code will be suspended(delay function suspends the current coroutine and invokes it after delay is finished). The current thread can be used for another process while the coroutine is suspended. When this coroutine is invoked(after the delay) a thread(maybe the old one) will pick up this coroutine and continue with line 14.

You shouldn’t think only delay functions, imagine that you listen to a system call after sending an HTTP request. You can suspend the coroutine when the request is sent and the coroutine can be invoked(assign a thread again) when the response comes via a system call. So you can use the threads with max efficiency.

Delay function automatically suspends the coroutine and invokes it, but if you want to do suspend and invoke process manually you should use suspendCoroutine function. Imagine integrating a reactive HTTP client library by using Kotlin coroutine.

suspendCoroutine { continuation ->
    this.httpRequest(request, options, object : Listener<Response> {
        override fun onResponse(response: SearchResponse) {
            continuation.resume(response) // Coroutine will be invoked(assign a thread here), and execution will continue.
        }

        override fun onFailure(e: Exception) {
            continuation.resumeWithException(e)
        }
    })
}

After the execution of this.httpRequest code, the current coroutine will be suspended, the code doesn’t wait for onResponse or onFailure events(because of the callback, they will be executed after the response is returned). When the response is successful, it invokes the coroutine again as in line 4 and the code continues from where it left off. So the thread was not blocked until a response came.

As you can see writing async non-blocking code in Kotlin is very easy, and you can easily integrate reactive libraries with Kotlin coroutines. Of course, there are a lot of advanced topics for coroutines but it’s not the subject of this article.

Asynchronous Flow

If you want to return multiple asynchronously computed values you can use Kotlin flows, which is similar to Go channels. If List is used to create these kinds of channels, it will be blocked because you need to fetch all the values at once, and then process it. But we want to do these stream operations asynchronously. Here is Flow to be used in these cases.

fun getFlow(): Flow<Int> = flow { // flow builder
    for (i in 1..3) {
        println("Preparing the value: $i")
    delay(100) // some blocking operation like database call
        emit(i) // emit value, imagine like add to channel
        println("Served the value: $i")
    }
}

getFlow().collect { value -> 
    println("Collected value: $value")
} 

// Output
Preparing the value: 1
Collected value: 1
Served the value: 1
Preparing the value: 2
Collected value: 2
Served the value: 2
Preparing the value: 3
Collected value: 3
Served the value: 3

Flow is cold stream, which means that the code inside the flow block will not be executed until a collect function call.

After the collect function is called, the values can be sent to flow via emit(value) function, and then the code inside the collect method which we just print the collected value will work.

fun getFlow(): Flow<Int> = flow { // flow builder
    for (i in 1..3) {
        println("Preparing the value: $i")
        delay(100) // some blocking operation like database call
        emit(i) // emit value, imagine like add to channel
        println("Served the value: $i")
    }
}

getFlow().collect { value -> 
    println("Collected value: $value")
    delay(1000) // long consumer process
} 

// Output
Preparing the value: 1
Collected value: 1
Served the value: 1
Preparing the value: 2
Collected value: 2
Served the value: 2
Preparing the value: 3
Collected value: 3
Served the value: 3

The delay is added to the collect method(line 12), to mock a long process. But as you can see the output didn’t change. The producer part(flow block) can produce(emit) more value but it is blocked because the consumer part(collect block) is blocked. Because emit function waits for all code given in collect to finish successfully, this feature is different from Go channels, in Go channels it will be blocked until the data is consumed by another goroutine.

If the producer code part is faster than the consumer part of the flow, you can use buffers. It creates 2 coroutines, one for the collect part and one for the flow part, so that you can consume and produce concurrently without breaking the order of values. Without the buffer, flow uses only 1 coroutine for these parts.

fun getFlow(): Flow<Int> = flow { // flow builder
    for (i in 1..3) {
        println("Preparing the value: $i")
        delay(100) // some blocking operation like database call
        emit(i) // emit value, imagine like add to channel
        println("Served the value: $i")
    }
}

getFlow().buffer().collect { value -> 
    println("Collected value: $value")
    delay(1000)
}

// Output
Preparing the value: 1
Collected value: 1
Served the value: 1
Preparing the value: 2
Served the value: 2
Preparing the value: 3
Served the value: 3
Collected value: 2
Collected value: 3

As you can see collected value order is not broken, but 2 and 3 values are prepared concurrently, and with buffer emit function doesn’t wait for all code given in collect to finish successfully, there are buffered channel between flow coroutine and collect coroutine so that collect method can keep and process values in order.

Collection Methods

Kotlin has a lot of useful collection methods, you can see the list from here. Let’s give an example, imagine that you want to get the first element from a list after filtering.

// Java
List<Integer> list = Arrays . asList (1, 10, 3, 7, 5);
int a = list . stream ()
    .filter(x -> x > 5)
.findFirst()
    .get();
System.out.println(a); // prints 10

// Kotlin
val list = listOf(1, 10, 3, 7, 5)
val a = list.first {
    it > 5
}
println(a) // prints 10

Both Java and Kotlin produce the same output, and there is no performance difference between them but Kotlin has a cleaner syntax. Kotlin has a lot of these kinds of useful methods, and I love using them.

If you wonder what is it and { }, you can checkout Higher-order functions from Kotlin docs to learn how they work.

Extension Functions

In Kotlin you can create extension functions easily. You can write extension functions to your existing class or built-in classes like String. Extension functions enable developers to add new features to an existing class.

class Circle (val radius: Double){
    fun area(): Double{
        return Math.PI * radius * radius;
    }
}

fun Circle.diameter(): Double{
    return 2 * radius;
}

val circle = Circle(5)
println(circle.diameter()) // prints 10

fun String.ascending() = String(toCharArray().sortedArray())
println("dcab".ascending())  // prints "abcd"

Inline methods

When Kotlin encounters the inline keyword, the inline function’s content is copied to where the inline function was initially called at compile time. This concept reduces the memory overhead. Simply cut the inline method code and paste it where it is called.
Let me give a basic example:

fun example( str : String, mycall :(String)-> Unit){
    println("*****")
    println(mycall(str))
    println("*****")
}
// main function
fun main(args: Array<String>) {
    println("Test: ")
    example("A Computer Science portal for Geeks",::print)
}

And bytecode of the main function:

public static final void main(@NotNull String[] args) {
  Intrinsics.checkNotNullParameter(args, "args");
  String var1 = "Test: ";
  System.out.println(var1);
  example("A Computer Science portal for Geeks", (Function1)null.INSTANCE);
}

As you can see in the line 5, the main function calls the example function directly. Let’s look at the same example using the inline function:

inline fun example( str : String, mycall :(String)-> Unit){
    println("*****")
    println(mycall(str))
    println("*****")
}
// main function
fun main(args: Array<String>) {
    println("Test: ")
    example("A Computer Science portal for Geeks",::print)
}

And bytecode of the main function:

public static final void main(@NotNull String[] args) {
  Intrinsics.checkNotNullParameter(args, "args");
  String str$iv = "Test: ";
  System.out.println(str$iv);
  str$iv = "A Computer Science portal for Geeks";
  int $i$f$example = false;
  String var3 = "*****";
  System.out.println(var3);
  int var5 = false;
  System.out.print(str$iv);
  Unit var6 = Unit.INSTANCE;
  System.out.println(var6);
  var3 = "*****";
  System.out.println(var3);
}

As you can see the code inside the example function is cut and pasted to the main function in compile time to avoid memory overhead.
However, be careful when using inline functions, large inline functions can increase the size of your files.

One pitfall of the inline functions is that they can’t be mocked.

Lazy delegated properties

Lazy initialization prevents the unnecessary initialization of objects. The properties will initialize after the first call, other calls won’t initialize just brings the initialized object. You can define lazy properties easily which execute the expression lazily.
Let me give an example:

// Java
String lazyValue = null;
String getLazyValue (){
    if (lazyValue == null) {
        System.out.println("computed!");
        lazyValue = "Hello";
    }
    return lazyValue;
}
System.out.println(getLazyValue()); // prints computed! and Hello
System.out.println(getLazyValue()); // prints Hello

// Kotlin
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}
println(lazyValue) // prints computed! and Hello
println(lazyValue) // prints Hello
// in higher order function code that does not equal any variable is returned. lazyValue field will be "Hello" after first call.

Static methods and class

Singleton pattern is widely used, Kotlin makes it easy for us to use it with Object.

object DatabaseConnectionProvider {
    fun getCouchbaseProvider() {
        println("Couchbase Provider")
    }
}

DatabaseConnectionProvider.getCouchbaseProvider() // prints "Couchbase Provider"

Also, like Java, you can create static functions and variables.

// Java
class Foo {
  public static int b = 5;
  public static int a() { return 1; }
}
System.out.println(Foo.b); // prints 5
System.out.println(Foo.a()); // prints 1

// Kotlin
class Foo {
  companion object {
     val b = 5
     fun a() : Int = 1
  }
}
println(Foo.b) // prints 5
println(Foo.a()) // prints 1

Returns and Jumps

In Kotlin you can break outer loops easily by using label expression.

// Java
loop: {
    for (...) {
        for (...) {
            if (some condition) {
                break loop;
            }
        }
    }
}

// Kotlin 
loop@ for (...) {
    for (...) {
        if (...) break@loop
    }
}

Also in Kotlin, you can assign properties as a result of the condition expression easily.

// Java
int max;
if(5 > 6){
    System.out.println("A");
    max = 5;
} else {
    System.out.println("B");
    max = 6;
}
System.out.println(max); //prints 6

// Kotlin
val max = if (5 > 6) {
    println("A")
    5
} else {
    println("B")
    6
}
println(max) // prints 6

val result = try {
    5
} catch(e: RuntimeException) {
    6
}
println(result) // prints 5

Scope Functions

In Kotlin, there are special methods to execute a block of code within the context of an object. There are five of them: let, run, with, apply, and also.

  • apply and also return the object(same object “it”).
  • let, run, and with return the lambda result.

let returns lambda result, also returns the context object(same object). But two of them have the same purpose: execute the block of code. run has the same purpose too, the only difference is that if you want to access the object, you should use “this” instead of “it” in run methods.

Also if you want to perform actions on a non-null object, use the safe call operator ?. on them. In this way, you can compute the block of code if the object is not null.

Most of the time I use let method with safe call operator. I think it’s enough to write a clean null safe executable code block.

val str = "Hello"

val a = str.let {
    println("The receiver string's length is ${it.length}") // prints "The receiver string's length is 5"
    "5555"
}
println(a) // prints "5555"
val b = str.also {
    println("The receiver string's length is ${it.length}") // prints "The receiver string's length is 5"
    "5555" 
}
println(b) // prints "Hello"

User("Mehmet", 20).let {
    println(it) // "it" variable is User("Mehmet", 20)
    it.name = "Sezer"
    it.age = 10
    println(it)
}
//Person(name=Mehmet, age=20)
//Person(name=Sezer, age=10, city=10)

val a = null
a?.let{ randomVariable ->
    println(randomVariable) 
}
// prints nothing because a is null

If you don’t want to use “it” variable you change it manually like “randomVariable”

Using apply, you won’t need to write the same object again and again if you want to update its properties.
with is very similar to apply method, the only difference is that with method takes the parameter of the object.

data class Person(var name: String, var age: Int, var city: String)

val person = Person("mehmet", 23, "ankara")
person.name = "sezer"
person.city = "istanbul"
person.age = 25

person.apply {
    name = "sezer"
    city = "istanbul"
    age = 25
}

println(person.name) // prints "sezer"
println(person.city) // prints "istanbul"
println(person.age)  // prints "25"

with(person) {
    println(name) // prints "sezer"
    println(city) // prints "25"
    println(age) // prints "istanbul"
}

I wanted to show you the basic fundamentals of Kotlin. Of course, there are a lot of advanced topics as well. I will try to dive deep into them in my future articles.

All feedbacks are welcome, thank you.

References

Kotlin Docs