
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