Step 1 of 5 · Reading · ~5 min
Learn
Strings and Null Safety
Null Safety
In most languages any reference can be null, so every method call is a coin flip that only
lands at runtime. Kotlin's answer is blunt: null is part of the type. A String can never
be null. A String? might be. The compiler knows which one you are holding and refuses to let
you confuse them.
val city: String = "Oslo"
val nickname: String? = null
println(city.length)
// println(nickname.length) // does not compile
That commented line is not a warning you can shrug off. It is a compile error — and that is the whole idea. The crash that would have happened in production happens on your machine instead.
The four tools
| Tool | Written | What you get when the value is null |
|---|---|---|
| Null check | if (x != null) x.length | you handle the branch yourself |
| Safe call | x?.length | the whole expression becomes null |
| Elvis | x?.length ?: 0 | the fallback you supplied |
| Assertion | x!!.length | a thrown NullPointerException |
Read that table as a preference order. The first three are safe. The fourth is a promise you are making to the compiler, and the compiler will hold you to it.
Smart casts: the check is enough
Once you have tested for null, the compiler remembers. Inside the branch the type is the non-nullable one, and you use the value normally — no cast, no unwrapping, no ceremony.
fun describe(label: String?): String {
if (label == null) return "no label"
return "label has " + label.length + " characters"
}
fun main() {
println(describe("kotlin"))
println(describe(null))
}
//> label has 6 characters
//> no label
After that early return, label is a plain String for the rest of the function. That is a
smart cast: you proved it, so the compiler upgrades the type for you.
Safe call and Elvis, side by side
fun main() {
val note: String? = null
println(note?.length)
println(note?.length ?: 0)
println(note?.toUpperCase() ?: "(none)")
}
//> null
//> 0
//> (none)
A safe call is contagious. note?.length is not an Int, it is an Int?, because it might
have produced nothing. Chain several of them and the whole chain collapses to null the moment
any link is null.
Elvis is how you climb back out to a normal type. Everything to the right of ?: runs only when
the left side turned out to be null. It is not limited to plain values either — return and
throw are expressions in Kotlin, so this is completely idiomatic:
val raw = readLine() ?: return
?.let — run a block only if there is something
fun main() {
val maybe: String? = "hello"
maybe?.let { println(it.length) }
}
//> 5
Had maybe been null, the block simply would not have run. Inside the block, it is the
non-null value.
The !! operator, honestly
!! converts a T? into a T and throws NullPointerException if you were wrong. It exists,
it is occasionally the right call, and it is also the most abused thing in the language.
✓ Defensible — the exercise guarantees an input line exists:
val n = readLine()!!.toInt()
✗ Not defensible — there is an obvious fallback sitting right there:
val name = readLine()!!
val name = readLine() ?: "anonymous"
Every starter in this course uses readLine()!! because our graders always supply the input the
exercise describes. In code that faces the real world, prefer ?:, ?.let, or an explicit
check. A habit worth forming: whenever you type !!, say out loud why the value cannot be null.
If you cannot finish the sentence, you have found a bug.
Text that might not be a number
Conversions come in two flavours, and this is where nullable types earn their keep.
| Call | On "42" | On "abc" |
|---|---|---|
.toInt() | 42 | throws NumberFormatException |
.toIntOrNull() | 42 | returns null |
val value = readLine()?.toIntOrNull() ?: 0
One line: read a line that might not exist, parse text that might not be a number, and fall back to zero if either goes wrong. That is the whole feature working as designed.
Your exercise
Length or Zero reads a single line and prints how many characters it holds.
The starter opens with readLine() ?: "" — Elvis doing exactly what the table above promises,
turning a missing line into an empty string so the rest of your code deals with a plain,
non-null String. The grader's second visible test sends an empty line and expects 0.
Two mistakes fail it. Keeping the value nullable and printing line?.length prints the word
null instead of 0. And swapping the starter's ?: "" for !! throws a
NullPointerException when there is no line at all, instead of printing a number. Print the
count on its own line with no label.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…