Getting started with Kotlin

In this Post, I will try to explain as simply as possible Kotlin basics for Java developers, who are interested in discovering the potential of this language.
In fact, Kotlin introduces great features that helps developer to be productive,
to avoid coding errors, to write beautiful code, in concise way and to extend the language by any useful feature.

1. Declaring Variables

When we use a language, the first thing that we could try to do, is declaring variables.
Kotlin promotes immutability, so it encourages the usage of immutable variables.
In order to instantiate an object, the good manner to do this, is the usage of the keyword val.

The keyword val means value and it is an immutable reference.
It is the equivalent of final reference in Java:

 val text = "Hello Kotlin"

When we use the val key word, we could not assign the variable text another value.

If we want to have mutable reference, we can use the key word var. Nevertheless, it is not the recommended coding style because it is error prone.

 var text = "Hello Kotlin, again :-)"

2. Type Inference

In kotlin, when the compiler has enough information to determine the type, we are not obliged to mention it.
For example, in the following example, we declare a variable that contains a magic number which is an Integer.
The compiler knows that we are talking about in Integer. Consequently, there is no need to declare the type.

val i = 1
println(i.plus(2))

3. Referential equality and structural equality

In Kotlin, the default == is mapped to the structural equality. What we mean by structural equality is comparing the object fields one by one based to the implementation of equals() and hashcode().
In Java, the default equality is referential one. If we would like to make referential equality in Kotlin, we should use the === operator.

4. String templates

4.1 Evaluating and concating variable

With Kotlin, injecting and evaluating variables in strings becomes very easy.
So, we want to display a text with a variable evaluated at runtime, we use it as following :

 val text = "Hello Kotlin"
 println("message: $text")

4.2 Evaluating variable containing paragraph

If we have a paragraph having newline characters or another special characters, we can evaluate it directly without any further escaping

   val paragraph = """
        Hello
        Kotlin
        🙂
    """.trimIndent()
    println(paragraph)

This feature can be very useful in unit tests for manipulating data like Json or XML


val json = """
{
	"title": "Programming Kotlin",
	"isbn": "isbn-test-1"
}
""".trimIndent()
println(json)

5. Default values vs overloading

In order to take benefits from overloading, in Java we should have many methods with same name but differ in the number of parameters.
The drawback of this approache is that we multiply the number of methods of the same name, which is error prone as we can make mistake when trying to choose the correct method to call.
Kotlin has solved this problem by introducing default values for variables in methods and constructors.
Here we have the following function with the parameter price having default value


fun newBook(isbn: String,
            title: String,
            price: Double = 0.0 ) {
...
}

If we call this function and we don’t know the price, then we can call it like this


val kotlinBook = newBook("isbn-test-1", "Programming Kotlin")

As we can notice here, we haven’t precised the price as we don’t know it.

6. Null Safety

6.1 Non nullable variable declaration

The null safety is a very interesting feature in Kotlin as it allows developers to have a stable and robust code.
The variables in Kotlin are not nullabe by default. So, we can’t assign null to it.
If we want so. We should declare it explicitly with ? symbol.


val book : Book = Book("isbn-test1", "Programming Kotlin")

Here, we can’t assign null to the variable book
If we want to assign null to this variable which is not recommended.
But, If we need it, we should declare the variable as following:

val book : Book? = null

6.2 Elvis Operator

Elevis operator is useful when we have a variable that is not nullable.
So, this variable will either take a value from the result of an expression or have a default value.
The following example illustrates this :

val result = BookRepository()
.findBookByIsbn("ISBN") ?: Book("ISBNTEST3", "Domain Driven Design")
println(result)

The findBookByIsbn() function returns a nullable reference. So, if this reference is null, we take the default value which the “Domain Driven Design” book.

7. Smart Casts

With smart cast, if we know the type of a variable, we are not obliged to cast it to the known type.

fun print(obj: Any) {
    if (obj is String) {
        println(obj)
    }
}

8. Import renaming

When we import classes, we can have many classes with same name but differs in package names. It can cause confusion.
Kotlin make it possible to rename an imported class in the context of another class.

import com.riadhmnasri.Book as BookDomain

The class Book could now be used as BookDomain class

9. Ranges/loops

Ranges in Kotlin are defined types. We can assign them to variables and pass them as parameters.

 val rangeInts = 1..10
 val  rangeInts2 = 1 until 10
 

10. Loops

In Kotlin, there are several ways to use loops.
Traditional Java way, can be used as following:

  for (i in rangInts) {
        println(i)
    }

But, we can use loops in more functional way:

    var names = listOf("Riadh", "Dimitri", "Alphano", "Eric", "Xavier")

    names.forEach {
        println(it)
    }

11. When expression/Sealed class

Sealed class are a kind of smart advanced enum. In sealed class, the compiler forces us to precise all the class hierarchy in the same file.
This constraint helps the compiler assert that we deal with the exhaustivity of the cases to handle in when expressions.
Sealed classes are often used with when expressions for pattern matching.

In the following example, we have a class hierarchy containing the result of a book repository.

sealed class BookResult {
    class BookNotFound(isbn: String) : BookResult()
    class BookFound(book: Book) : BookResult()
}

If we use this sealed class as a result of a function, we should handle all the cases indicated in the hierarchy of the class

    val resultSearch = BookRepository().findBookByIsbn("ISBNTEST1")
    when (resultSearch) {
        is BookResult.BookNotFound -> println("Sorry, Not found!")
        is BookResult.BookFound -> println("Cool !")
    }

So, if remove the case BookResult.BookFound in the when expression, we should have a compiler error.
This feature is very useful for code extensibility and maintainability.

12. Inheritance

In Kotlin, by default all the classes are final. But, if we want to inherit from a class. We should precede the class name with the keyword open.
We should also precede the function to override, with the keyword open.
In the sub-class we should precede the function to override with the keyword override

// Inheritance
open class Vehicle {
    open fun name(): String {
        return "Vehicle"
    }
}

class Car : Vehicle() {
    override fun name(): String {
        return "Car"
    }
}

13. Delegation

Kotlin promotes delegation because it is better for code maintainability.
For further information about delegation, you can take a look at articles talking about debate
inheritance versus composition

// Delegation
interface Hobby {
    fun name(): String
    fun cost(): String
}

class Tennis : Hobby {
    override fun name(): String = "Play Tennis"
    override fun cost(): String = "Expensive"
}

class Activity(hobby: Hobby) : Hobby by hobby {
    override fun name(): String = "Activity :: Play Tennis"
}

Here, we have a class Hierarchy which implements different concret hobbies.
The class Activity implements the same interface Hobby as the concret hobbies, and, delegates the effective creation of hobbies to the concret hobbies classes.

    val activity = Activity(Tennis())
    println("${activity.name()}-${activity.cost()}")

14. Named Parameters

When we have functions with several parameters, kotlin allows to give names of each parameter. When calling a function, the name of parameter corresponds to its name in the function signature.
This feature avoid making errors when passing parameter to functions especially when the types are the same.
Another advantage is that we can pass the parameters in any order.

Example 1:

   val result = BookRepository().findBookByIsbn(isbn = "ISBNTEST1")
   println(result)

Example 2:

   val kotlinBook = Book( title = "Programming Kotlin",
                          isbn = "ISBNTEST1",
                          price = 30.0)
   println(kotlinBook)

15. Extensions functions

We usually use external libraries in our code. In many cases, we would like adding some behaviour to a class. We can do this in Java by creating another classes that enrich the target class using the Decorator pattern, for example.
This is approach is verbose because it introduces new classes.
Kotlin has introduced a amazing feature which is extensions functions.
So, we can add extra functions to a class without modifying it.
For example, imagine we have an external domain class called Book . If we want to add the ability to rate books, we can simply do it by declaring and implementing an extension function.

fun Book.rate(note: Int): String = when (note) {
    in 1..5 -> "★".repeat(note)
    else -> "Invalid note"
}
  val book = Book("ISBNTEST1", "Programming Kotlin")
  println(book.rate(5))

16. Scoping functions

Kotlin provides a set of ready to use extension functions.
They are available for all object types.

16.1 Apply

Use apply function when you want to process the instance and return it.

  // Returns the original instance
    val book = Book("ISBNTEST1", "Programming Kotlin")
               .apply { println("rate: ${rate(5)}") }
    println(book)

16.2 Let

The let scoping function, serves at applying a function to the instance and returning the result.

// It's similar to map() function (for collections)
    val book = BookDomain("ISBNTEST1", "Programming Kotlin")
               .let { it.rate(5) }
    println(book)

16.3 With

The with takes the instance in parameter, applies a function to it and return the result.

    val book = BookDomain("ISBNTEST1", "Programming Kotlin")
    val noteBook = with(book) {
        rate(5)
    }
    println(noteBook)

16.4 Run

The run function applies a function to the instance as a receiver and returns its result.

    val book = BookDomain("ISBNTEST1", "Programming Kotlin")
    val noteBook = book.run {
        rate(5)
    }
    println(noteBook)

16.5 Lazy

The lazy function initialise a variable lazily with the result of a function passed as a lambda.

   // Lazy
    val note = lazy { BookDomain("ISBNTEST1", "Programming Kotlin").rate(5) }
    println(note.value)

16.6 Use

The use function is useful to initialise a variable with value retrieved from a resource and closed at the end.

    val text = ClassPathResource("/data/content.txt").inputStream.use { it.bufferedReader().readText() }
    println(text)

17. Collections

In Kotlin, there are 2 kinds of collections:

  • Immutable Collections (the default result)
  • Mutable Collections : we should use them explicitly

17.1 Iterating lazily over collections

In the following example, the list used is immutable as we have precised nothing special. It’s the default choice. Kotlin promotes the usage of immutable collections as they are thread safe and help us have robust and stable code.

    val ints = listOf(5, 2, 6, 4, 7, 9, 8) // Immutable
    val result = ints.asSequence()
                     .filter { it % 2 == 0 }
                     .also { println(it) }
                     .toList()
    println(result)

Notice the usage of the function asSequence(). Therefore, the collection will be processed lazily. It is the equivalent to the stream() method in Java.

17.2 Immutability

If we has a real need to use a mutable collections, we should use the MutableList implementation.

    val mutableListInts = mutableListOf(5, 2, 6, 4, 7, 9, 8)
    mutableListInts.clear()
    println(mutableListInts)

As we are using a mutable list, we can use functions that changes the content of the collection such as clear()

18. Operator overloading

Kotlin make it possible to overload operators. Unlike scala, Kotlin team has chosen to limit the list of the operators to overload as they have estimated that there is no need to allow the operator overloading of any string.
In the following example, we are showing how it is simple to overload the addition operator of any objet and we can give the operator any meaning that we want.
Here, the addition operator of 2 books, concatenate ISBNs and titles and calculates the total price of the books to add:

operator fun Book.plus(book: com.riadhmnasri.domain.Book) = copy(isbn = this.isbn + book.isbn, title = this.title + book.title, price = this.price + book.price)
  val kotlinBookToAdd = Book("ISBNTEST1", "Programming Kotlin", 30.0)
  val dddBookToAdd = Book("ISBNTEST2", "DDD", 40.0)
    println(kotlinBookToAdd + dddBookToAdd)

19. DSL

A powerful feature in Kotlin, is the ability to create custom DSL “Domain Specific Language”.
In this article, we will show a quick demonstration of the potentiel of Kotlin in making specific DSLs.
Based on infix and extension functions, we will create an assertion DSL that allow us to assert that two types are equal.
Here, we implement the infix extension function that will do the job.

infix fun  T.shouldBeEqualTo(expected: T?): Boolean = this == expected

Now, we can call our function that asserts the equality as following :

    println("★★★★" shouldBeEqualTo Book("ISBNTEST1", "Programming Kotlin", 30.0).rate(5))

20. Conclusion

In this Post, I have showed a quick overview of Kotlin Potential.
The main amazing features that we have discovred are:
– Null safety
– Immutability
– Pattern matching
– extension functions
– DSL

I hope I have given you the will to try to discover and may be use this language not only in Android development but also in backend development.
All the examples in this article are available in my Github: here

Don’t hesitate to comment, if you have any question or maybe proposals and ideas to improve this article.

Leave a comment

I’m Riadh

Welcome to my technical blog !

Let’s connect