8 Kotlin Features That Make Your Life Easier

Sam Collins
Tenable TechBlog
Published in
9 min readNov 25, 2020

In this article we’ll explore 8 Kotlin features that make the language a pleasure to use. You won’t want to make another pull request to a Kotlin application without reading this first.

Smooth sailing

When it comes to Java virtual machine (JVM) languages, there are a lot of choices nowadays. Until Kotlin was released back in 2016, many developers saw no reason to ever depart from ol’ reliable Java, especially with the usefulness of many recent Java additions (looking at you lambdas). With Kotlin, JetBrains has created a language that is remarkably easy for a Java developer to start using, and once you start using Kotlin it’s awfully hard to stop.

Some of the following features are mostly for convenience and readability while others are innate properties of the language which make it safer and more reliable, the latter are the primary reasons why Tenable engineering chose to make Kotlin our default JVM language. The features are ordered by their importance with more critical features towards the end of the post. Without further ado, here are eight Kotlin features that make coding in it a pleasure.

1. Default method args and naming method args

This one you may have seen in other languages like JavaScript. When defining a Kotlin function, you can apply default parameter values thus making it optional for the caller to supply that value. This can help reduce the need for function overloading. For example, the function can offer a default parameter that changes the behavior of the function slightly if a non-default value for that parameter is present (see `doubleSpace` in the example).

Also, as the caller you can make your code more readable by specifying the parameter name when calling a Kotlin function. This can help reduce the chance of supplying same-type parameters in the wrong order.

Example of default args and named parameters:

Note: unless the omitted parameters are at the end of the function call, you will have to name every parameter in the function call.

2. Data classes

How often do you write a POJO and then have to slog through adding getters, setters, equals, hashcode, and toString methods to it? Even using the auto-generation of your IDE (which isn’t always great) for those methods you still end up with a lot of boiler-plate code for something that should be intuitive. Those methods come free with a Kotlin data class. Data classes also give you the ability to deep copy an existing instance of the class and specify which fields you want to be different in the copy.

If you have used Lombok, you’ll see some familiar stuff. The keyword here is the `data` prefix in front of `class`. Example:

As you can see from that example, you can also override the auto generated methods.

A few limitations/gotchas of data classes are that they:

  • cannot be abstract, open, sealed, or inner
  • cannot extend another data class
  • implement a reference based comparison of arrays in the auto-generated equals method instead of a structural comparison ( meaning `arr1 == arr2` vs `arr1.equals(arr2)`)

3. Extension functions

Extension functions are another one you may have seen in other languages but they are nonetheless great for improving your code flow. Extension functions allow you to define what would be a function parameter as a `receiver` so that you can call a function on a certain type. This allows you to supplement an existing class with new functionality, which is especially useful if you don’t control the classes’ definition or want to keep the new functionality separate from the core object. Extension functions are also great for chaining together method calls to improve readability. Example:

Extension functions are further covered in another Tenable blog post discussing readability in Kotlin, Conversational Kotlin: A Look at the Benefits of Readable Code.

4. Collection utils

Collections in Kotlin have all kinds of cool built in methods to condense your code and keep it easily readable. Similar in practice to the streams introduced in Java 8, you can map, flatMap, forEach, and do all sorts of other cool stuff on collections. Using these built in functions is trivial to understand and comes naturally to a functional programmer. Example:

Under the covers, Java’s streams are more similar to Kotlin’s Sequences than its Collection utils, more on that here.

5. Smart casting and type inference

Explicit casting of a variable to a certain type is an uncomfortable thing to have to do, Kotlin takes some of that burden away from you with this feature. The compiler will go down possible logic branches and if it’s safe, cast variables to different types (including non-null type) in order to prevent you from needing to check type or nullability for each usage or needing to create a new variable which casts the original like in Java. Here’s an example:

6. Coroutines

Asynchronous programming isn’t always a breeze and it can be a challenge in Kotlin too, but there are some really useful things in the Kotlin coroutines library that make multitasking simpler.

Kotlin compares coroutines to “lightweight threads” and that philosophy is reflected in their implementation. When you define a block of code in the scope of a coroutine, you’re really giving Kotlin the blueprints for a task that Kotlin then breaks down into sub-tasks to be executed in that same order but not necessarily all at once. Kotlinlang.org had a great banner illustrating this concept:

The major players in Kotlin coroutines are

  • suspend functions — asynchronous functions, what allows thread sharing
  • runBlocking {} — run this code while blocking the main thread
  • launch {} — launch a coroutine in the background and continue execution
  • async {} — begin a coroutine that will eventually produce a result, the result can be captured with `await()`

The element of coroutines that really improves your life is the ease of implementation and readability. For instance, when defining a suspend function you don’t need a special return type or wrapper like in Java. When calling functions from a coroutine scope, you can freely call any other coroutine functions like you normally would in a synchronous block.

There are also more practical reasons to favor coroutines over threads. Coroutines have the advantage of not being threads themselves. A coroutine is like an individual job that executes concurrently inside of thread(s). If you wanted to accomplish something concurrently with threading, you would need one thread per logical job whereas in Kotlin coroutines you could have each logical job be a separate coroutine that could run in one or more threads. Creating threads can be rather expensive so you’d typically expect to see better performance in coroutines over threads. Let’s look at an example

Here we can see that the coroutines didn’t execute synchronously but more importantly, only 2 threads were used. Now for the equivalent in Java using threads

You can see that 10 threads were used, one for each logical job.

Coroutines are a dense topic but one that is worth learning more about. This listicle only scratches the surface but you can check out the Kotlin docs on the topic here.

7. Null-safety

Look familiar? The NullPointerException can really ruin your day and throw a wrench into even the most well thought out data flows. To remedy this, Kotlin allows every object type to have two flavors, nullable or not. Objects are defined as nullable (or not) upon declaration using a `?` and that aspect of the object can never change. For example:

Objects declared with a `?` are the nullable flavor of that type whereas a type declaration with no `?` means the object can never be null. If a non-null object is used in a situation where it could be made nullable, the code will not compile.

Kotlin provides some nifty ways to work with null-safety. The `?.` (often called the null-conditional operator) can be used to access a value only if the value is non-null. The `?:` (or Elvis operator) is shorthand for checking if an object is null without an `if` statement. Here are some examples:

The following is the same code but in Kotlin versus Java:

Kotlin:

Java:

It’s often a code smell, but worth mentioning that the `!!` operator lets the developer declare that a nullable object will be non-null when it gets accessed (one of the only ways to cause a NullPointerException in Kotlin). One example of when you might want to use `!!` is when working on an element of a collection of objects after filtering the collection for non-null values.

8. Java interoperability as a first class citizen

This one is pretty straightforward but nonetheless critical. Kotlin works so smoothly with Java that you’ll most likely never even have to think about accessing Java from Kotlin or vice versa. Some things to keep in mind:

a. Java is not null-safe. So when using an object declared in Java, it’s safer to treat the object as nullable. Intellij will warn you about this if you are assuming a Java object is non-null.

b. Getter and setter methods should be replaced with a Kotlin property reference. Change `object.getThing()` to `object.thing`. If the type of the field is a Boolean the property accessor will be in the form `object.isThing`. For example:

c. If a Java library uses a Kotlin keyword for something, the keyword must be escaped with backticks. For example:

This is also useful for giving tests in Kotlin ‘full English names’. For example:

d. Array typing is stricter in Kotlin. For example, passing an array of a subclass as an array of a superclass is prohibited. Example:

Using Kotlin in a Java app really couldn’t be easier, but check out the Kdocs page on the subject for a more in-depth review of the subject.

Conclusion:

The idiomatic nature of Kotlin combined with its ease of use and integration into existing JVM applications makes it likely that Kotlin will be around for the long haul. These eight features only scratch the surface of the Kotlin world. To learn more check out https://kotlinlang.org/docs/reference/.

Bonus: Kotlin Scripting: you can write .kts scripts in Kotlin and compile them for quick tasks. This is nice when you need something small and don’t feel like looking up syntactical nuances of Python or Ruby.

--

--