Null Safety
Nullable and Non-Nullable types
Normal types, like String
, are not nullable. To make them able to hold null values, you have to explicitly denote that by putting a ?
behind them: String?
var string : String = "Hello World!"
var nullableString: String? = null
string = nullableString // Compiler error: Can't assign nullable to non-nullable type.
nullableString = string // This will work however!
Safe call operator
To access functions and properties of nullable types, you have to use special operators.
The first one, ?.
, gives you the property or function you’re trying to access, or it gives you null if the object is null:
val string: String? = "Hello World!"
print(string.length) // Compile error: Can't directly access property of nullable type.
print(string?.length) // Will print the string's length, or "null" if the string is null.
Idiom: calling multiple methods on the same, null-checked object
An elegant way to call multiple methods of a null-checked object is using Kotlin’s apply
like this:
obj?.apply {
foo()
bar()
}
This will call foo
and bar
on obj
(which is this
in the apply
block) only if obj
is non-null, skipping the entire block otherwise.
To bring a nullable variable into scope as a non-nullable reference without making it the implicit receiver of function and property calls, you can use let
instead of apply
:
nullable?.let { notnull ->
notnull.foo()
notnull.bar()
}
notnull
could be named anything, or even left out and used through the implicit lambda parameter it
.
Smart casts
If the compiler can infer that an object can’t be null at a certain point, you don’t have to use the special operators anymore:
var string: String? = "Hello!"
print(string.length) // Compile error
if(string != null) {
// The compiler now knows that string can't be null
print(string.length) // It works now!
}
Note: The compiler won’t allow you to smart cast mutable variables that could potentially be modified between the null-check and the intended usage.
If a variable is accessible from outside the scope of the current block (because they are members of a non-local object, for example), you need to create a new, local reference which you can then smart cast and use.
Eliminate nulls from an Iterable and array
Sometimes we need to change type from Collection<T?>
to Collections<T>
. In that case, filterNotNull
is our solution.
val a: List<Int?> = listOf(1, 2, 3, null)
val b: List<Int> = a.filterNotNull()
Null Coalescing / Elvis Operator
Sometimes it is desirable to evaluate a nullable expression in an if-else fashion. The elvis operator, ?:
, can be used in Kotlin for such a situation.
For instance:
val value: String = data?.first() ?: "Nothing here."
The expression above returns "Nothing here"
if data?.first()
or data
itself yield a null
value else the result of data?.first()
.
It is also possible to throw exceptions using the same syntax to abort code execution.
val value: String = data?.second()
?: throw IllegalArgumentException("Value can't be null!")
Reminder: NullPointerExceptions can be thrown using the assertion operator (e.g.
data!!.second()!!
)
Assertion
!!
suffixes ignore nullability and returns a non-null version of that type. KotlinNullPointerException
will be thrown if the object is a null
.
val message: String? = null
println(message!!) //KotlinNullPointerException thrown, app crashes
Elvis Operator (?:)
In Kotlin, we can declare variable which can hold null reference
.
Suppose we have a nullable reference a
, we can say “if a
is not null, use it, otherwise use some non-null value x
”
var a: String? = "Nullable String Value"
Now, a
can be null. So when we need to access value of a
, then we need to perform safety check, whether it contains value or not. We can perform this safety check by conventional if...else
statement.
val b: Int = if (a != null) a.length else -1
But here comes advance operator Elvis
(Operator Elvis : ?:
). Above if...else
can be expressed with the Elvis operator as below:
val b = a?.length ?: -1
If the expression to the left of ?:
(here : a?.length
) is not null, the elvis operator returns it, otherwise it returns the expression to the right (here: -1
). Right-hand side expression is evaluated only if the left-hand side is null.