Background:
The Elvis operator (?:) appeared in Kotlin as a concise and safe way to handle nullable values. It got its name because of its resemblance to Elvis Presley's hairstyle when viewed from the side. The goal is to eliminate the boilerplate code of the form if (a != null) a else b and make daily development more convenient.
Problem:
Direct access to a value that may be null leads to a runtime error (NullPointerException). Therefore, a tool is needed to elegantly substitute default values when a variable turns out to be null.
Solution:
The Elvis operator applies to nullable types. If the expression to the left of the operator is not null, it returns that expression; otherwise, it returns the expression to the right. This makes the code more compact and safe.
Code example:
fun getLength(str: String?): Int { return str?.length ?: 0 } val result = getLength(null) // result == 0 val result2 = getLength("Hello") // result2 == 5
Key features:
What happens if an expression with a side effect is to the right of the Elvis operator? Does it always get executed?
No! The expression to the right is only evaluated if the left side is null. This can be crucial if the function has side effects or "expensive" computations.
Code example:
var called = false val x: String? = "test" val y = x ?: run { called = true; "default" } // called will be false, because run won't even execute
Can the Elvis operator be used to throw exceptions?
Yes, in Kotlin you can write like this:
fun getLengthStrict(str: String?): Int = str?.length ?: throw IllegalArgumentException("str is null")
If str is null, an exception will be thrown. This is a convenient data validation mechanism.
Can multiple Elvis operators be "chained" together?
Yes, this is a common approach for fallbacks:
val name = fromDatabase ?: fromCache ?: "Unknown"
This expression will return the first NON-null value or the string "Unknown".
Multilevel nesting of null-checks via Elvis:
val title = a?.b?.c?.d?.e ?: defaultTitle
Pros:
Cons:
Explicit decomposition step by step with clear variable names:
val deepValue = a?.b val deeperValue = deepValue?.c?.d ?: default
Pros:
Cons: