Lesson 4 / 25

Nullable Types and Safe Operators

String versus String?, ?., ?: and !!.

Null is part of the type

In Kotlin a String can never hold null; a String? can. The compiler refuses to call members on a nullable value directly. The safe call a?.length returns null if a is null. The elvis operator a ?: default supplies a fallback (it can also return or throw). The not-null assertion a!! converts to non-null and throws a NullPointerException if the value is null, so use it rarely. After if (a != null), the compiler smart casts a to the non-null type inside the branch, as long as it is a local val or a value it can prove does not change.

Nulls become compile-time errors

Kotlin separates nullable and non-null types and narrows types automatically after checks.

Three ideas: nullable types and safe operators, when and ranges, type checks and casts.
Figure 2.1 — Nullable value, safe call, elvis default, non-null result.

Handling a missing value

Safe call, elvis and smart cast.

data class User(val name: String, val email: String?)

fun emailDomain(user: User): String {
    val email = user.email ?: return "no email"     // elvis with early return
    return email.substringAfter("@")               // email is String here
}

fun nameLength(name: String?): Int {
    if (name != null) {
        return name.length            // smart cast to String
    }
    return 0
}

fun shout(text: String?): String? = text?.uppercase()   // null stays null

Treat !! as a code smell

Each !! is a place where you told the compiler "trust me". Prefer ?: with a sensible default, an early return, or requireNotNull(x) { "message" } which throws with a clear message.

Quick check: What does `user?.email ?: "none"` evaluate to when `user` is null?

  • null
  • "none"
  • It throws NullPointerException
  • An empty string
Answer

"none" — The safe call yields null and elvis supplies the fallback.