Kotlin Uncovered: Part 4

Learning about Kotlin nullability through decompilation

Apple Laptop Work by Unsplash is licensed under CC0

I’ve found decompiling Kotlin bytecode into Java to be a great tool to help understand what’s going on. If this is new to you, check out Part 1. I’ll wait here for you to get back.

One of the things we hear about a bunch when it comes to Kotlin is that nullability is built right into the type system. This means that we always know when something could be null, and the compiler forces us to check for it. By doing this, we run a much lower risk of encountering a NullPointerException.

We notate that a variable could be null by putting a question mark after the type, e.g. String?. If there is a question mark, it’s nullable. Otherwise, you’re safe. If a variable is nullable, and we don’t check for null before we call something on it, it won’t compile. Such is the case below. Since maybeString is nullable and we don’t check for null before we call length on it, it won’t compile.

// Won't compile!!
var maybeString: String? = "Hello"
maybeString.length

Safe call operator

Because that one won’t compile, we’ll use a different example to decompile. This one uses the safe call operator. That’s the question mark after maybeString, and before .length. What this does is call length if maybeString is not null and return null otherwise.

// Kotlin
val maybeString: String? = "Hello"
maybeString?.length

When I decompiled this into Java, it confused me at first. I didn’t see any null safety!

// Java
String maybeString = "Hello";
maybeString.length();

This is where I remembered of all the work the compiler does for us. Let’s look at an example where we don’t know if maybeString is null. We’ll use a method with the signature fun getString(): String?to set this value.

// Kotlin
val maybeString: String? = getString()
maybeString?.length
// Java
String maybeString = this.getString();

if(maybeString != null) {
  maybeString.length();
}

Okay, the null check is there this time. Because we were assigning a non-null value to an immutable variable in the first example, the compiler could conclude that there was no way for maybeStringto be null. As a result, it removed the extra code for us. If the compiler is doing this, imagine all the other ways it’s helping us!

!! Operator

If you’re looking for a NullPointerException, Kotlin does have an option for that. By using the !! operator, we’re telling the compiler that we are confident that it’s not null, so we can use the value of a variable without checking for null first.

// Kotlin
val maybeString: String? = getString()
maybeString!!.length

When we decompile it into Java, we can see that there is an explicit check for null, and it throws the NPE if needed before our code even gets to call anything on it.

// Java
String maybeString = this.getString();

if(maybeString == null) {
  Intrinsics.throwNpe();
}

maybeString.length();

Null Safe Scoping

By combining the null safe operator with a higher order function, such as let, we can get null safe scoping. We can pass a lambda to let, and it is only executed if the value is not null. Otherwise, it does nothing. Inside the block, the value is assigned to a variable, it.

// Kotlin
val maybeString: String? = getString()

return maybeString?.let {
  // it == maybeString
  it.length
}

When we decompile it, we can see that it uses a ternary operator inline to check if maybeString is null before returning the length or null. It’s a bit underwhelming with this small code sample, but it can be useful when you have multiple lines of code that depend on a value not being null. With more lines, it would also use an if/else block.

// Java
String maybeString = this.getString();

return maybeString != null ? Integer.valueOf(maybeString.length()) : null;

Elvis Operator

One last null safety option we’ll look at here is the Elvis operator. By combining it with the safe call operator, we can easily give a default value if maybeString is null. Here, we are saying that if maybeString is null, return zero.

// Kotlin
val maybeString: String? = getString()
return maybeString?.length ?: 0

Then, when we decompile it, we get the ternary we might expect. If maybeString is not null, return the length, otherwise, return zero.

//Java
String maybeString = this.getString();

return maybeString != null ? maybeString.length() : 0;

Kotlin provides greater null safety than Java and gives us some clean tools for us to work with nullability. Lesser risk for NPEs is one of the many reasons I enjoy working with Kotlin.


Need to go back and refresh your memory on other parts of the series? You can find them below:

Part One

Part Two

Part Three

Photo of Victoria Gonda

Victoria is a software developer working on mobile and full stack web applications. She enjoys exchanging knowledge through conference talks and writing.

Comments

  1. JD
    June 11, 2017 at 0:39 AM

    When we decompile it, we can see that it uses a ternary operator inline. With more lines, it would also use an if/else block.

    It’s not the compiler who selects if/else or ternary, it is the decompiler choosing how to represent the same conditional bytecode constructions.