A Kotlin Schrödinger Cat

How the K-language compiles things that can blow your mind.

Alex Facciorusso
Kt. Academy

--

Everyone knows Kotlin.
I mean, at least you have heard its name if you are in the IT world.

And if you heard its name, you know that it is a JVM-based language developed by JetBrains since 2011 and now it’s becoming one of the most used languages in the J̶a̶v̶a world. Or at least now you know.

Today I want to write on nullability checks that Kotlin does “under the hood”, why sometimes our beloved language deserves a little bit of attention to avoid mistakes and how big-K translates our pretty code in JVM bytecode.

In particular, we have to give extreme attention when we are forcing non-null objects on Kotlin, because everything could — really! — end in a NPE (aka Null Pointer Exception, aka “dead”).

Meowww…? 🐱

Let’s start. We have a box that contains a cat, and we kill that cat.

No, I was joking, but if you are interested, here it is a link to the explanation of the Schrodinger's cat paradox. Anyway if you don’t clap this article a tiny lovely kitten will die. Or maybe not. Or maybe both.

First thing, we define our Cat object

class Cat {
val meow = "Meowww...? :3"
}

and begin with a boring instance of our Cat:

val aBoringCatInstance: Cat? = Cat()
aBoringCatInstance!!

IntelliJ (and so Android Studio) allows us to inspect our generated JVM bytecode in any moment, and also to decompile it in Java code. There’s an article by Svyatoslav Chatchenko that explains how to do it.

Looking at that code, we can see that in the second line we are just forcing our Cat instance (defined as nullable), to be non-null. What follows is the result of the code compiled in JVM and de-compiled in Java:

new Cat();

And…what? Nothing. We are not using aBoringCatInstance in any other part of our application, Kotlin is smart, knows it and so optimizes our code, calling the constructor without creating a variable and so without allocating it on the stack (mind that the object will be anyway allocated on the heap, but it will be able to be garbage-collected as soon as possible since there’s no pointer on the stack “pointing” to that object).

In the next two lines of code we are feeling a little bit masochistic:

val sureNullCat: Cat? = null
sureNullCat!!

As you can imagine, as we are trying to “force cast” our null cat to a non-null type, and naturally the code will explode. Or at least throw a NULL POINTER EXCEPTION!

Cat sureNullCat = null;
Intrinsics.throwNpe();

This is the Java decompiled from the Kotlin code we just saw. We can imagine what throwNpe() will do, but there’s something strange: why are we just throwing a null pointer exception without any check? Because Kotlin is smar… ok I can’t say it again, but you know.

Our badass compiler knows that, in that point, that variable is for sure null, and since we are forcing a null pointer exception, it just please us with what we want (and deserve). So optimization is the word. Or optimisation if you prefer.

Let’s open the box 📦

Finally, here we are, observing the cat going out from our box, dead or alive.

Let’s define a method — ahem… function — that tosses a coin. Depending on the result, it will return a Cat instance or a null value:

fun openBox(): Cat? {
return if (Random().nextInt(2) == 0) Cat() else null
}

This function alone can’t do anything, so we are going to open that box, and doing something we should never do:

val maybeACat = openBox()
val meow = maybeACat?.meow!!

Let’s analyse why what we are doing here is extremely wrong.

Initially we could think that meow!! will not trigger a null pointer exception, because meow is a non-nullable property of Cat . And about this we are right, but in Kotlin, every child object of a nullable object will “inherit” that nullability (nullability is not really inherited, but you get the idea).

So the meow property will become effectively a String? if an ancestor is of a nullable type, also if in our Catobject it’s defined as String .

This will make meow!! a potential nuke bomb, because if maybeACat will result in a null value, meow will be null as well, and forcing it to a non-null value will result in what we know very well.

Let’s see what our cute Kotlin compiler will generate in JVM, and translate it to Java:

Cat maybeACat = openBox();
if ((maybeACat != null ? maybeACat.getMeow() : null) == null) {
Intrinsics.throwNpe();
}

In this code there’s nothing special to explain: we are just creating the maybeACat variable, and if that variable or the result of its getter getMeow() (the counterpart of our meow property in Kotlin) is null, we will throw an NPE.

Here’s the complete code, just for reference.

I hope to have cleared some doubts about Kotlin’s internal behaviours. Remember to press and to not press the 👏🏻 button if you liked (or not) this post!

Sorry for the long post, here’s a photo of a kitten! :3 (photo by Nicolas Suzor from Brisbane, Australia)

To be up-to-date with great news on Kt. Academy, subscribe to the newsletter, observe Twitter and follow us on medium.

--

--