Aside from the ordinary types of classes shown in previous sections, Kotlin allows several other types of classes:
Throwable is the superclass for exceptions and errors, just as in Java. A Throwable will unwind the call stack until it is caught, or until the program (or thread) is completely unwound and terminates. Error and Exception are both open classes, and may be inherited.
An Error represents a condition that an application should not try to catch; usually an abnormal condition, such as the VM running out of memory, or a class missing from the class path. In theory, it is impossible to recover from either of those conditions. The most common method of handling Error is to let it terminate the program, writing a useful log message. Then you can fix whatever happened to your build.
In Kotlin, all exceptions are unchecked. This is a clear deviation from Java, where only RuntimeException and its descendants are unchecked. Because Kotlin, in the JVM, can interface with Java classes, those classes may need to expect an exception. For this reason, the @Throws annotation may be used to declare "checked" exceptions. (This is also useful for interfacing with languages like Swift and Objective-C in Kotlin/Native.)
Unlike Error, there will be times when you want to catch an Exception and recover from the condition. The most common situation is handling "evil" input data: just print a message to the console and/or log, and ignore the bad input.
When your code encounters an exceptional condition, you may throw an exception yourself. The first step is to create the exception. The zeroth step is to choose the type of exception to throw:
The constructors for standard exception classes come in four general flavors:
Some standard Exception types don't come with the flavors that take the cause parameter. These types are assumed never to be caused by another condition. When an exception is logged, it often includes a stack trace; if the throwable had a cause, that cause appears with its own stack trace. You should rarely (or never) call the no-parameter constructor. Create a useful message; your successors will thank you, and you might even thank yourself.
If you write your own exception class, you should probably include at least the constructor with a message parameter. Include the cause parameter if you might wrap a different Throwable.
If you ever catch and then re-throw an exception, throw a new exception with the caught exception as the cause.
In general, if a method has no idea what to do with an exception, it should not catch it. Rather, let the call stack unwind until it reaches a method that can handle the issue.
Different exceptions might be handled with different catch blocks; or sometimes with the same block. Consider:
try {
<something>
} catch (EOFException ex) {
<handle EOF>
} catch (FileNotFoundException | RemoteException ex) {
<handle those two>
} catch (IOException ex) {
<handle non-specific IOException>
}
You may have as many types separated by | as make sense for the catch block.
Note that the catch (IOException ex) block is last. If it came before EOFException's or FileNotFoundException's catch, you would never enter those catch blocks, because they are subclasses of IOException. Always catch the more specific exception first. The base class Throwable should only be caught explicitly as a last resort, and then only in a debugging situation, to figure out what was actually thrown and why.
Notes:
One more thing:
In Kotlin, an almost any block, including try/catch, is an expression that has a value, which can be assigned to a variable. Consider:
var answer: Int
try {
answer = question() // but question() can throw an exception...
} catch (ex: SomeException) {
println("question threw something, defaulting to 1")
answer = 1
}
Even though we wanted answer to be val, it needed to be var just so we could later change it. Later, we add some code, and (inadvertently) modify answer. That's a subtle bug...
Instead, just treat the try as an expression:
val answer = try {
question()
} catch (ex: SomeException) {
1
}
If the program gets to the end of the try block, the expression's value is the value of the last expression in the try. If a SomeException is caught, the expression's value is the value of the last expression in that catch block. (If the exception is caught elsewhere, the value is moot, since the assignment was aborted and stack has unwound.) If there is a finally block, it has no effect on the value of the try expression.
Kotlin offers some efficient ways to check for invalid conditions, and throw exceptions if those conditions are met.
fun bePositive(value: Int) {
require(value > 0) { "I needed a positive integer, but I got $value" }
// ...
}
val optionalFoo: Optional<Foo> = maybeGetFoo()
check(optionalFoo.isPresent) { "Foo is missing! }
fun firstOfTwentyQuestions(val animalVegetableOrMineral: String): Int {
return when (animalVegetableOrMineral) {
"animal" -> 1
"vegetable" -> 2
"mineral" -> 3
else -> error("Unexpected type")
}
Veterans of C++ and its concept of Resource Acquisition Is Initialization (RAII) will miss the concept of a destructor that releases resources. Since Java uses garbage collection instead of destruction, and GC happens only when necessary, something else is needed.
Any try block (with or without catch blocks) may have a finally block as well. The statements in the finally block will be executed when either:
Resource rez;
try {
rez = acquireResource();
<statements>
} catch(Exception ex) {
<more statements>
} finally {
<even more statements>
releaseResource(rez);
}
The finally block can be used to release any resource acquired for the try. Make sure that any exceptions thrown from the finally block are handled, and that your resources are really released. Whether there are catch blocks before the finally block, or elsewhere in the stack chain, is an implementation detail.
Since Java 8, the try with resources form has added a shorthand for releasing resources. A similar facility exists in Kotlin: the use function.
val myFile = File(path)
val textLine = myFile.bufferedReader().use { reader -> reader.readLine() }
The bufferedReader method returns a BufferedReader (surprised?). Because BufferedReader implements the interface AutoCloseable, the reader will be closed when the lambda exits, just as if by a finally block.
Integer and floating-point math in Kotlin is identical to math in Java. Float and Double are, respectively, 32-bit and 64-bit IEEE 754 binary floating-point numbers, and if they are non-nullable, are represented by the float and double primitives; and BigDecimal is available.
Kotlin provides operator overloads for BigDecimal and BigInteger, so it isn't necessary to call methods like BigDecimal.add() and .multiply() explicitly.
On other platforms, it is theoretically possible that integers use something other than Two's Complement arithmetic, but I have not heard of a modern processor that uses One's Complement or BCD, or anything even more extreme.
Does anybody really know what time it is? (Does anybody really care? Is it 25 or 6 to 4?)
If you are working with dates and times, please understand time zones and the class kotlinx.datetime.TimeZone. Using a TimeZone, you can convert between Instant and LocalDateTime. Create a TimeZone by
val zone = TimeZone.of("America/Los_Angeles") // for example
As for writing your own date/time handling code, I have one word of advice: Don’t. It’s much, much harder than it looks. Here’s an entertaining and educational video describing the issues.
The argument to TimeZone.of() corresponds to the zones defined in /usr/share/zoneinfo on Unix systems.
kotlinx-datetime
For simple operations with calendars, consider using the library kotlinx-datetime. This package contains just about everything needed to simplify date and time calculations, including operator overloads for computing differences in time, and conversions to java.time objects. To include the package in your project:
Maven
dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-datetime-jvm</artifactId>
<version>0.6.1</version>
</dependency>
Gradle
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1'
}
(Keep the version up to date, of course.)
A nice tutorial on kotlinx-datetime is here.