Idiomatic Kotlin: Elvis Operator
This article is a part of the Idiomatic Kotlin series. The complete list is at the bottom of the article.
In today’s article, we will talk about writing Elvis operator and how it can make our code rock (and roll).
What is the Elvis operator?
The Elvis operator in Kotlin is an operator that receives two inputs and returns the first argument if it is non-null or the second one otherwise. It is fancily called the null-coalescing operator. It is a variant of the ternary operator but for null-safety checking.
Motivation
Kotlin, as you may know, supports nullability explicitly. This is an attempt to minimize the dreaded NullPointerException
during runtime and catch them all at compile time. If you are writing pure Kotlin code, then chances are you may not be going to deal with nullable types. But the Java JDK, Android framework and other Java libraries will force you to deal with them. Let us look at how verbose it is to check for nullability in Java.
In Java, reference types can always be null. Therefore, you have to check them for nullability everytime to make sure that you will not encounter NullPointerException
at runtime. This is equivalent to 3 lines minimum of code for every instance. And trust me, this can easily get out of hand in nested instance cases.
Kotlin introduced a lot of operators to make nullability checks idiomatic, and easier to read. One of these operator is the Elvis operator. We are singling out the Elvis operator because I think it is one of the most underused feature (I, too, am guilty as well) in the collection of Kotlin nullability check tools.
How to use the Elvis operator?
The syntax of the Elvis operator is ?:
. A simple example would be:
Wait a minute. I thought it is a variant of the ternary operator. Then why am I seeing two inputs only? As we have defined above, the main purpose is for nullability checking, so the null check is implicit. The expanded operation will look something like this
One of the main strengths of Elvis operator is its use in conjunction with the safe call operator ?
. As a background, you can access a nullable objects’ properties or methods by simply using the safe call operator. However, using the safe call operator will imply that the resulting type will be nullable as well. Let me show you what I mean through an example.
Notice that the age
is now a nullable type. This is because the john
instance is a nullable Person
. Even though you are sure that the john
instance is not null, the compiler cannot assume because it’s type is nullable. Therefore, the safe call operator here only provides an easy syntax when checking and accessing the age property of the Person
class but the output would still be of nullable type. Elvis operator provides options in avoiding propagation of nullable types.
First option is to provide default non-null value.
Now we can compile the same function successfully. This is because we made sure that a default non-null value will be assigned to age in case the john
instance is null and the compiler inferred it correctly.
Another option is to throw error. The beauty of the Elvis operator is that the second argument can be an expression, not just a variable.
The compiler is still smart enough to infer that the age is non-nullable because if in case it was, an exception will be thrown.
Elvis operator under the hood
Let us try to decompile this code below and see how it translates to Java code.
The decompiled version is below
No magic here guys. It still performs a series of null checking down to the deepest child. We can therefore say that there is no added overhead, and no performance improvement either.
Check out the other articles in the idiomatic kotlin series. The sample source code for each article can be found here in Github.
- Extension Functions
- Sealed Classes
- Infix Functions
- Class Delegation
- Local functions
- Object and Singleton
- Sequences
- Lambdas and SAM constructors
- Lambdas with Receiver and DSL
- Elvis operator
- Property Delegates and Lazy
- Higher-order functions and Function Types
- Inline functions
- Lambdas and Control Flows
- Reified Parameters
- Noinline and Crossinline
- Variance
- Annotations and Reflection
- Annotation Processor and Code Generation