Expressions with Nullable Types

Of course, there will be times when we have to cope with null. We want to do certain things if the value is not null, and do something else if the value is null. There are a variety of things in Kotlin that can help you work with null values.

Intrinsically Safe Stuff

There are many things in Kotlin, supplied by the language or its standard library of classes and functions, that support null. This includes things that you might not expect.

For example, CharSequence? (from which String? inherits) has an isNullOrEmpty() function. As one might expect, it returns true if the value is null or has no characters (e.g., it is the empty string, ""). So, this works:

fun foo(message: String?) {
  println(message.isNullOrEmpty())
}

fun main() {
  foo("Hello, world!")
  foo("")
  foo(null)
}

We get:

false
true
true

Not everything is allowed — many functions are defined on the core non-nullable type, rather than on its nullable counterpart.

So, for example, Int has a dec() function that returns the value decremented by one. That is defined on Int, not Int?, and so this code does not work:

val one = 1

println(one.dec())

val maybeZero : Int? = null

println(maybeZero.dec())

But, since this is a compile-time error, there is no harm in trying! And the compile error hints at a couple of solutions to the problem:

error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?
println(maybeZero.dec())
                 ^

Safe Calls

Normally, to call a function on an object, you use a dot notation (.).

One option for calling a function on a variable, parameter, or property that is of a nullable type is to use the safe-call operator (?.). Then, one of two things will happen:

So, this works:

  val one : Int? = 1

  println(one?.dec())

  val maybeZero : Int? = null

  println(maybeZero?.dec())

This prints:

0
null

So, the first dec() call happens as normal, because one is not null, and the second dec() call is replaced by null, as maybeZero is null.

The type of the result of a ?. function call is a nullable edition of whatever type the function call normally returns. Calling dec() on an Int returns an Int, but calling dec() on an Int via ?. returns an Int?.

This means that you can chain ?. calls:

  val three : Int? = 3

  println(three?.dec()?.dec()?.dec())

  val maybeZero : Int? = null

  println(maybeZero?.dec()?.dec()?.dec())

This prints the same result:

0
null

The Elvis Operator

?. function calls behave differently depending on whether the “receiver” (the object on which you are calling the function) is null or not.

Similarly, the “Elvis operator” — ?: — returns different values depending on whether the left-hand side of the operation is null or not:

  val one : Int? = 1

  println(one ?: "um, this should not be printed")

  val maybeZero : Int? = null

  println(maybeZero ?: "Elvis has not left the building")

This prints:

1
Elvis has not left the building

In the first println() call, one is not null, so we print the value of one. In the second println() call, maybeZero is null, so we print the string that appears to the right of the operator.

Note that return and throw are valid things to have on the right-hand side of an Elvis operator:

fun printOrNot(value: Int?) {
  val nonNullable = value ?: return

  println(nonNullable)
}

fun main() {
  val one : Int? = 1

  printOrNot(one)

  val maybeZero : Int? = null

  printOrNot(maybeZero)
}

Here, we only get one line of output:

1

In the case where we call printOrNot() with a null value, the first line of the function will return, bypassing the println() call.

But now, a quick FAQ:

Why Is This Called the “Elvis Operator”?

If you turn your head to the side and look at the ?: operator, it looks a bit like a pair of eyes, above which is a pompadour hairstyle. Elvis Presley famously wore his hair in a pompadour in his early career.

Who is Elvis Presley?

Ask your parents.

My Parents Are Asking: Who is Elvis Presley?

Elvis Presley was an American rock-and-roll icon of the 1950’s through 1970’s.

Isn’t This FAQ a Bit of a Sidetrack for a Programming Book?

Don’t be cruel.

Null Checks and Smart Contracts

Kotlin’s compiler can help avoid some of the pain of dealing with nullable types. If the compiler knows that something cannot be null, due to some prior check, it relaxes the rules requiring safe calls (e.g., ?.).

For example, let’s see if our Int? is even, odd, or null:

fun evenOrOddOrNull(value: Int?) {
  if (value != null) {
    if (value.rem(2) == 0) {
      println("Even!")
    }
    else {
      println("Odd!")
    }
  }
  else {
    println("Null!")
  }
}

fun main() {
  val one : Int? = 1

  evenOrOddOrNull(one)

  val maybeZero : Int? = null

  evenOrOddOrNull(maybeZero)
}

rem() is a function on Int that takes another Int, divides the two, and returns the remainder. So, rem(2) will return 0 for even numbers and 1 for odd numbers.

This prints what you might expect:

Odd!
Null!

However, it may not be obvious why this even compiles. value is an Int?. It would seem that value.rem(2) should fail with the same sort of compiler error that we saw earlier in the chapter (“only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?”).

However, we only try calling rem() on value inside of a null check (if (value != null)). Kotlin’s compiler knows that inside of that block, value cannot be null, because we just checked to see if it was null or not. Kotlin’s compiler therefore relaxes the safe-call requirement, and we can just use . for our rem() call, rather than ?. (and have to deal with a potentially null result).

This only works when the compiler is sure that the value cannot be null, though. That happens quite a bit, but there will be cases when the compiler cannot be certain and defaults to the safe-call requirement:

fun numberizer(): Int? = 1

fun evenOrOddOrNull() {
  if (numberizer() != null) {
    if (numberizer().rem(2) == 0) {
      println("Even!")
    }
    else {
      println("Odd!")
    }
  }
  else {
    println("Null!")
  }
}

evenOrOddOrNull()

Here, we get our number from a numberizer() function. That function is declared to return an Int?, even though its implementation happens to always return 1. Kotlin’s compiler does not attempt to examine the implementation of numberizer(). It looks at the return type, sees that it is Int?, and assumes that it could be null. More importantly, just because if (numberizer() != null) succeeded and we went into the if block, the compiler has no guarantee that some future numberizer() call will return a non-null value, so it gives us a compile error for numberizer().rem(2), demanding a safe call there.

Dammit, It’s Not Null

Sometimes, though, you know better than the compiler. You are sure that a certain value is not null, even though the compiler thinks otherwise.

For that, there is !!. You can use this operator at the end of a value or expression, and it asserts to the compiler that the value or expression will not be null, even if from a type standpoint it could be.

The previous example is a case where you know that numberizer() could never return null, but the compiler does not. The right solution in this case would be to have numberizer() return Int instead of Int?. However, there will be cases where you do not have control over the return type of the function, so you cannot change it.

We can “fix” the previous example another way, via !!:

fun numberizer(): Int? = 1

fun evenOrOddOrNull() {
  if (numberizer() != null) {
    if (numberizer()!!.rem(2) == 0) {
      println("Even!")
    }
    else {
      println("Odd!")
    }
  }
  else {
    println("Null!")
  }
}

evenOrOddOrNull()

Here, we append !! to the second numberizer() call, to force the compiler to treat it as returning an Int instead of as returning an Int?. Hence, this snippet compiles and runs.

We also saw this in the preceding chapter, where we were forcing a NullPointerException:

  var thisIsReallyNull: String? = null

  println(thisIsReallyNull)
  println(thisIsReallyNull!!.length)

Here, we are asserting that thisIsReallyNull is not null via !!. That results in a NullPointerException, since thisIsReallyNull is really null.

!! is a bit of a “code smell”. Use it sparingly, as if you are ever wrong in your assertion, you will crash with a NullPointerException or the equivalent, as we did in the above snippet.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.