What Hides Behind the Question Mark? Diving into Nullable in Kotlin
\n\nHave you ever written code in Java and caught a NullPointerException at the worst possible moment? If so — you know how painful it is. Kotlin developers solved this problem once and for all by making null safety not just a feature, but a language philosophy. But beginners are often scared by the question: "In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them?" (this is exactly how the most popular question on Stack Overflow sounds).
Today we will break down all the idioms for working with nullable: from simple ?. to tricky let and magical !!. And most importantly — we'll look under the hood to understand how it works at the bytecode level. Let's go!
Quick Primer: What is Nullable?
\n\nIn Kotlin, types are divided into two camps: nullable (can be null) and non-nullable (cannot). If you write var name: String = null — the compiler will throw an error. But var name: String? = null — that's fine. The question mark is a marker that says: "Hey, compiler, I know this might be empty, don't yell at me."
But how do you work with this? Simply checking for null with if is boring and not Kotlin-like. Let's look at the real tools.
The Lifesaving Trio: ?, ?: and ?.
\n\nKotlin offers three pillars on which all nullable work rests. Remember them — they will save you hours of debugging.
\n\n1. Safe call operator (?.)
\n\nThis is the most frequent guest in your code. The safe call operator allows you to call a method or property only if the object is not null. If the object is null — the operator returns null, and no exception.
\n\nval user: User? = null\nval name: String? = user?.name // name = null, no error\n\nUnder the hood, this turns into bytecode that does a regular null check and conditional call. No magic, just safety.
\n\n2. Elvis operator (?:)
\n\nElvis is your friend when you need to provide a default value. If the left side is not null — take it, otherwise — take what's on the right.
\n\nval name: String? = null\nval displayName: String = name ?: "Guest" // "Guest"\n\nElvis can also be used for early return from a function:
\n\nfun getUserName(user: User?): String {\n val name = user?.name ?: return "Anonymous"\n return name\n}\n\n3. Not-null assertion (!!)
\n\nThis is the rebel operator. It says: "I'm sure this is not null, and I'm ready to die (meaning, throw an NPE) if I'm wrong." Use it with caution, only when you are 100% sure.
\n\nval user: User? = getUserFromDatabase() // we are sure it exists\nval name: String = user!!.name\n\nUnder the hood, !! compiles into a regular check that throws NullPointerException on null. Nothing complicated, but dangerous.
Idiomatic Way: let, run, apply and Company
\n\nNow we move to the most interesting part. The Stack Overflow question is exactly about this: how to idiomatically transform nullable values. The answer — extension functions from the standard library.
\n\nlet — King of Nullable Transformations
\n\nThe let function executes a block of code only if the object is not null. Inside the block, the object is available as it (or you can rename it).
val user: User? = getUser()\nuser?.let { \n println("Username: ${it.name}")\n // Here it is guaranteed not null\n}\n\nThis is the idiomatic way to perform an action only on a non-null value. Without let, you would have to write:
if (user != null) {\n println("Username: ${user.name}")\n}