Reading — step 1 of 7
Learn
Errors and try/catch
This course has already told you about four things that throw. "4x".toInt()
throws. list[5] throws. reduce on an empty list throws. !! on null throws.
What it has not shown you is what to do about any of them.
fun main() {
val good = try { "42".toInt() } catch (e: NumberFormatException) { -1 }
val bad = try { "4x".toInt() } catch (e: NumberFormatException) { -1 }
println("$good $bad")
//> 42 -1
}
Read that shape carefully, because it is the Kotlin-specific part: try is an
expression. It has a value, so it can sit on the right of a val, exactly like
the if and when expressions you already use. The value is the last line of
whichever block ran.
What the exception carries
catch (e: NumberFormatException) binds the thrown object to e, and its
message tells you what the input actually was:
fun main() {
try {
"4x".toInt()
} catch (e: NumberFormatException) {
println(e.message)
}
//> For input string: "4x"
}
Catching the right type
Each of those four failures throws a different type. Catching too broadly hides bugs; catching too narrowly misses the failure. The ones this course has already mentioned:
| What you wrote | What it throws |
|---|---|
"4x".toInt() | NumberFormatException |
listOf(1, 2)[5] | ArrayIndexOutOfBoundsException, a kind of IndexOutOfBoundsException |
listOf<Int>().reduce { .. } | UnsupportedOperationException |
nullable!! | KotlinNullPointerException, a kind of NullPointerException |
require(false) | IllegalArgumentException |
Catching the parent type catches the child, which is why
catch (e: IndexOutOfBoundsException) is the right net for a bad index even
though the object is an ArrayIndexOutOfBoundsException.
finally
A finally block runs on the way out no matter which path was taken — value
returned, exception caught, or exception still travelling:
fun probe(s: String): String =
try { s.toInt(); "ok" } catch (e: NumberFormatException) { "bad" }
finally { print("finally($s) ") }
fun main() {
println(probe("7") + " " + probe("x"))
//> finally(7) finally(x) ok bad
}
Often you do not need to catch at all
Kotlin's standard library usually offers a total function instead of a throwing one, and reaching for it is the more idiomatic choice:
fun main() {
println("4x".toIntOrNull())
println(runCatching { "4x".toInt() }.isFailure)
//> null
//> true
}
toIntOrNull() returns null rather than throwing, which hands the problem to
the null-safety tools you already know. Use try/catch when the failure is
genuinely exceptional, or when you need the message.
Common mistakes
- Catching
Exceptionfor everything — it hides the bug you did not expect along with the one you did. - An empty catch block — the program continues in a state you never reasoned about.
- Forgetting
tryis an expression — assigning in both branches works, butval x = try { .. } catch { .. }is shorter and cannot leavexunassigned. - Using
try/catchwheretoIntOrNull()would do — parsing user input is expected to fail, so it is not exceptional.
Your exercise
Parse or Report reads a count, then that many lines. For each line, print
ok: <value> when it parses as an Int, and bad: <message> when it does not —
where <message> is the exception's own message, which for 4x reads
For input string: "4x". Finish with sum: of the values that parsed and bad:
of how many did not.
Because the failing lines must report the exception's own text, toIntOrNull()
cannot do the job here — you need the caught object.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…