Features of Exceptions
Exceptions have two key features that distinguish them from the approaches considered previously.
First, an exception is an object of a distinct type, used specifically to signal errors1. The type of the exception conveys information about the nature of the error, and further details can be provided via data stored inside the exception object.
Second, an exception cannot be ignored. When an exception occurs, it fundamentally changes the flow of control within a program. If no code is found to intercept and deal with an exception, it will halt the program.
Information Transfer
Exceptions are implemented as classes, with names that indicate the kind of error that they represent. These classes are typically organized into a hierarchy:
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Exception <|-- RuntimeException
RuntimeException <|-- ArithmeticException
RuntimeException <|-- IllegalArgumentException
RuntimeException <|-- IndexOutOfBoundsException
IllegalArgumentException <|-- NumberFormatException
Note that classes lower in the hierarchy represent more specialized types of error.
Aside from conveying information via the name of its class, an exception has
a String property named message that can be used to give further details
of the error. This property can be given a value when you create an exception
object.
For example, the variance() function discussed previously could signal an
insufficient amount of data using this exception object:
IllegalArgumentException("not enough data")
The class name tells us that the argument supplied to variance() is not
suitable, and the string provides details of why that is the case.
The standard exception types will often meet your needs, but sometimes it is useful define your own exception class, and Kotlin makes this very easy to do:
class MyException(message: String) : Exception(message)
There are basically three reasons why you might want to do this:
- You want to convey more information about the error, via the name of the class
- You want to be able to easily intercept this specific type of exception
- You want to add more information to the exception, beyond a basic error message
For example, consider the writeToFile() function discussed earlier. If
writing to the file fails, you might want to indicate this via a dedicated
exception that includes the line number on which failure occurred. You
could define a suitable exception class like this:
class TextException(msg: String, val line: Int) : Exception(msg)
Don’t worry about understanding the syntax here. We will discuss it properly when we cover classes and inheritance later.
Flow Of Control
-
Open the file
Control.kt, which is in thetasks/task9_2subdirectory of your repository. Study the code, then compile and run the program.Examine the output. This shows you that
main()calls the functionfirst(), which in turn callssecond(). This stack of function calls then ‘unwinds’:second()finishes normally, thenfirst(), thenmain(). -
A line of
second()that causes an exception has been commented out. Uncomment it now, then recompile the program and run it again. How has the behaviour changed?
The key point to note from this demo is that none of the messages about
leaving a function are printed. When the exception occurs inside
second(), it prevents any of the other code from running2. The only
way to regain control over program execution is to catch the exception.
The additional information displayed here is known as a stack trace. It shows details of the call stack at the time of the exception, and is very useful for debugging.
When you see a stack trace like this, work your way down from the top, moving pass any references to library code in which the exception may have originated, until you reach the first reference to your own code. This will give you a filename and a line number, showing you where you can begin your investigation into what caused the error.