An introduction to Kotlin

At at Google I/O 2017, Kotlin was thrown into the spotlight. The language would be given first class support in the android ecosystem. At the time, Kotlin was only “released” for just over a single year. Since this announcement, Kotlin has become the defacto language for developing on Android.
More recently, Kotlin is making moves into new areas such as native desktop applications, web applications (front-end and back-end) and soon, the Apple ecosystem.

Kotlin isn’t just for android

More than ever, Kotlin is being adopted as a language. A 2021 statistica survey of over 31,000 participants found that 14% had used Kotlin in the last 12 months and 9% had plans to adopt/migrate to Kotlin. That is up 100% over a year when compared to a similar survey a year earlier.

As a language that found its first “big break” within the Android ecosystem, Kotlin has been spreading its wings. JetBrains’ dev ecosystem survey shows that 52% of developers are working on web backend while, as expected, 63% are developing for Android.

In the years to come, Kotlin’s mutliplatform programming could swing the balance yet again, as developers will be able to develop native applications for Linux, Mac OS, Windows, IOS, Android and the web all with a common codebase.

Kotlin is maturing

Kotlin is still a young language and this trend continues with the age of developers that use Kotlin. The Koltin census from 2020 noted that 47% of developers were under the age of 30. Of these developers, 40% now have 2+ years of experience with Kotlin.

It is becoming much easier to call yourself “A Kotlin dev” when taking a look at the job market. In fact, Kotlin is now considered to be the 18th most used/interacted with languages over both Github and Stack Overflow. Still a large distance behind the likes of Java, Kotlin is slowly gaining traction as more developers jump into the ecosystem. Even though Kotlin is behind Java, the interoperability means that Java libraries are fully functional in Kotlin!

Redmonk language rankings show Kotlin at 18th

Language features

Coming from a Java or C# background, a developer will be somewhat familiar with and be able to get started relatively quickly with Kotlin.
The following section will briefly address some of the language features that might be unfamiliar. If not unfamiliar, hopefully these features are interesting and a reason for you try kotlin yourself.

Immutability

Immutability is achieved in many ways in Kotlin. The increased immutability reduces magic (unexpected mutations) and forces developers to opt-in to the more dangerous and potentially harmful world of mutable data.

val and var

Kotlin introduces val and var. You might be able to guess what these mean at a glance.

val: an immutable/read-only local variable

var: a mutable local variable

What is great about val and var is that both of these keywords are the same in length. This means it is super easy to scan through your variables.

Immutable by default

When working with Kotlin, immutability is the default behaviour. When you create a list listOf("a", "b", "c"), we are creating an immutable list. If you so desire to create a mutable list, you must opt-in.
The same list would look like mutableListOf("a", "b", "c") when made in a mutable way.

Data class

Java is soon to adopt this language feature. The data class prevents the need for boilerplate and IDE auto-code-generation.
A data class gives you getters and setters (should your variables be var) along with the usual toString(), hashCode(), equals() and copy().

copy() is not a regular method found in a Java object. This is another move towards immutability. Using this method, we can create a new object keeping some fields, while changing others.

1
2
3
4
5
6
7
8
data class Person(
val name: String,
val age: Int,
val likesKotlin: Boolean
)

val john = Person("John Doe", 30, false)
val johnsLikeKotlin = john.copy(likesKotlin = true)

Default values and named parameters

Have you ever had a class with multiple constructors that take a different selection of fields?
With Kotlin and default parameter values, this becomes something of the past. We can set backup values for parameters not passed into a function or constructor. You can also pick and choose what parameters to pass using named parameters.

1
2
3
4
5
6
7
8
9
data class Person(
val name: String,
val age: Int,
val eyes: Int = 2,
val legs: Int = 2,
val likesKotlin: Boolean = false
)

val somePerson = Person(name = "John", age = 25, likesKotlin = true)

Null safety

Kotlin tries to eliminate the NPE (in most cases).
We can still use nullable values, however, we are able to work with these values in a safe way.

1
2
val nonNullableString: String = "hello"
val nullableString: String? = null

The type system will not allow you to set a non nullable type (types not denoted with a ?) to null and will throw a compilation error.

Using a ?, we are able to work on and chain function calls onto nullable data.

1
2
nonNullableString.length // returns 5
nullableString?.length // returns 5

Should you have a nullable piece of data, you can default this to a value of your choice using the “Elvis operator”, named after the king of rock and roll himself.
hint: turn the operator on its side

1
val anotherString = nullableString ?: "fallback"

anotherString would be set to “fallback” and will be non nullable in type. From now on, the type system will know that this variable is of type String thanks to smart casting.

Smart casting

Casting values explicitly can be cumbersome and wordy. In kotlin, type checking and casting is done in one step. The casting is kept over the local context of an operation (so long as the compiler can ensure a value’s type will never be mutated).

We introduce the is keyword here which, as I am sure you will be able to guess, is a type check operator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fun demo(x: Any) {
if (x is String) {
print(x.length) // x is automatically cast to String
}
}

fun demo(x: Any) {
if (x !is String) return

print(x.length) // x is automatically cast to String
}

fun demo(x: Any) {
// x is automatically cast to String on the right-hand side of `||`
if (x !is String || x.length == 0) return
}

fun demo(x: Any) {
// x is automatically cast to String on the right-hand side of `&&`
if (x is String && x.length > 0) {
print(x.length) // x is automatically cast to String
}
}

We can also use a kotlin when block, using the following pattern, to work over many different possible types of some x.

1
2
3
4
5
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}

Explicit casting is still possible using the as keyword. This is usually an unsafe operation and is therefore a smell. However, this can be useful in situations such as unit testing.

Operator overloads

Have you ever wanted your objects to have symbolic methods like +, -, *, / or other common operators?
With operator overloads any class in kotlin may use these operators. We do not need to have functions called .add() etc. To do this, we use the Kotlin keyword operator when writing out our functions, along with a pre-defined naming convention for the function name.

1
2
3
4
5
6
7
8
9
10
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}

fun main() {
val counter = Counter(2)
println(counter + 8) // returns "Counter(dayIndex=10)"
}

Checked exceptions

Maybe one of the most hated features of Java, checked exceptions have been slowly phased out in the new Java APIs such as Java 8 streams. You can read my articles on the result pattern. These articles go into more detail about the negatives of checked exceptions and the options available to you as a kotlin developer.

Kotlin has done away from checked exceptions and instead, you can throw and catch at any time within the language. You can still interface with Java (either calling Java from Kotlin or Kotlin from Java) should you need to.

Kotlin-Java and Java-Kotlin interop

As mentioned a few times already, Kotlin works amazingly well within a Java code base. Thanks to its bidirectional interoperability, you can migrate from Java to Kotlin file-by-file, class-by-class. This also means that all of the greatest (and worst) Java libraries can be used within a Kotlin only project!

If you want to make sure your code is callable within Java, there are some annotations you can supply such that your code will compile down with the needed metadata required. Head over to the docs to find out more.

Coroutines

You can think of coroutines as lightweight threading. Each coroutine is a suspendable computation that can run on any thread, suspend and then resume on any thread. A coroutine is not tied to a specific thread.

With Kotlin’s coroutines, you can (although it is not advisable to do so) create hundreds of thousands of coroutines. In Java, you would simply run out of memory trying to concurrently spin up this many threads.

Along with this “lightweight threading” comes many extra benefits. Structured concurrency, channels and multiple styles of writing asynchronous computation. The topic is far too large to explain in a small highlight section, so you should check out the documentation if you are interested in learning more. There is a great introduction to coroutines guide that will show you a good number of concepts.

Extension functions

Have you ever had an external class do almost everything you wanted? That little bit of extra functionality that you desire doesn’t need to be placed in some wrapper object. In Kotlin, you can extend functionality of an class using extension functions.

1
2
3
4
5
6
7
8
fun String.addFullStop(): String {
var before = this
return before + "."
}

fun main() {
println("Hello, world".addFullStop()) // Returns "Hello, world."
}