Kotlin中的异常处理跟Java和许多其他语言非常相似。一个函数能够按正常完成,否则当发生错误时抛出一个异常。函数调用者可以捕获这个异常并处理它。如果不这样做异常将会在未来的调用栈中重新抛出。 在Kotlin中,异常处理声明的基本形式跟Java非常相似。你以一种不令人迷惑的方式抛出异常:
if (percentage !in 0..100) {
throw IllegalArgumentException(
"A percentage value must be between 0 and 100: $percentage")
}
和其他类一样,你不必使用new
关键词来创建一个异常的实例。
跟Java不同,在Kotlin中throw
语法是一个表达式,同时被用作其他表达式的一部分;
val percentage =
if (number in 0..100)
number
else
throw IllegalArgumentException( // ‘throw’是一个表达式
"A percentage value must be between 0 and 100: $number")
在这个示例中,如果满足条件,程序能够如期的运行,同时percentage
变量被number
变量初始化。否则,将会抛出一个异常。变量也不会被初始化。我们将会在后续的空类型(The nothing type)一章讨论有关throw
作为另一个表达式的一部分的技术细节。
2.5.1 try
,catch
和finally
跟在Java中一样,你使用带有catch
的try
语法和finally
从句来处理异常。你可以在接下来的示例中看到。从一个给定的文件中读取一行,(然后)尝试着去把读入的数据解析为一个数字,并返回数字,或者如果这一行不是有效的数字时返回null
:
fun readNumber(reader: BufferedReader): Int? { // 1 你不需要显式的声明这个函数会抛出哪些异常
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) { // 2 异常的类型放在右边
return null
}
finally { // 3 finally跟Java的中一样
reader.close()
}
}
>>> val reader = BufferedReader(StringReader("239"))
>>> println(readNumber(reader))
239
(Kotlin)跟Java最大的不同就是throws
从句并没有在代码中体现出来:如果你用Java写这个函数,你将在这个函数声明的后面显式的写上throws IOException
。你不需要这样做是因为IOException
是一个已被检查的异常(checked exception)。在Java中,这是一个需要显式处理的异常。你必须声明你的函数可以抛出的所有已检查的异常。如果你是从另一个函数中调用达到,你也需要处理它的已检查的异常或者声明你的函数可以抛出这些异常。
就像许多其他现代的JVM语言,Kotlin没有区分已检查和未检查的异常。你没有指定函数抛出的异常,你可能会,也可能不会处理任何异常。这个设计的决定是基于Java使用已检查异常的实践(经验)。经验表明,Java规则通常强制使用一大堆无意义的代码来重新抛出异常或者忽略异常,然而,这些规则并没有一致的使你避免可能发生的错误。
例如,在我们刚刚看到的代码中,NumberFormatException
是一个没有被检查的异常。因此,Java编译器并不会强制你捕获这个异常,你很容易在运行时看到这个异常的发生。这是很不幸的(事情)。因为无效的输入数据是一种常见的情况,同时这种异常应该被优雅的处理。与此同时,BufferedReader.close()
方法可能会抛出一个未检查但是需要被处理的IOException
。如果一个流关闭失败了,大部分程序不会采取有意义的行动。所以代码强制要求在close()
中函数捕获异常固定的套路。
那对于Java 7 的try-with-resources
(语法)呢?Kotlin并没有为此准备任何特殊的语法:它是通过一个库函数来实现的。在资源管理和λ(lambdas for resource management)一章,你将会看到为什么这是可行的。
2.5.2 try
作为一个表达式
为了了解Java和Kotlin之前的另一个明显的不同,让我们来对这个案例做一些小修改。让我们移除finally
代码块(因为你已经明白了它是如何工作对),并添加一些代码来打印你从文件中读取的数字:
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine()) // 1 变成了try表达式的值
} catch (e: NumberFormatException) {
return
}
println(number)
}
>>> val reader = BufferedReader(StringReader("not a number"))
>>> readNumber(reader) // 2 什么也不会打印
如你所见,Kotlin中的try
关键字就像if
和when
一样,引入一个表达式,你也可以把它的值赋给某个变量。不同于if
(的地方),你必须把声明主体放在闭合的大括号里。和其他声明一样,如果主体包含多个表达式,try
表达式的值作为一个整体的值是最后一个表达式的值。
在这个例子中,我们在catch
块中放了一个return
声明,所以在catch
块后面方法不会继续执行。如果你想它继续执行,catch
从句也需要有一个值。而这个值将会是它里面最后一个表达式的值。以下就是它的工作原理:
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine()) // 1 当没有异常发生时使用这个值
} catch (e: NumberFormatException) {
null // 2 当发生异常时使用null值
}
println(number)
}
>>> val reader = BufferedReader(StringReader("not a number"))
>>> readNumber(reader)
null // 3 抛出了一个异常,因此函数打印''null"
如果try
代码块正常运行,try
代码块中的最后一个表达式就是结果。如果捕获了一个异常,catch
代码块中相应的最后一个表达式就是结果。在之前的一个例子,如果捕获了一个NumberFormatException
,那么返回值是null
。
如果此时你已经不耐烦了,你可以开始与你在Java相似的方式来用Kotlin编程。如果你继续读这本书,你将会了解到如何改变你思考的习惯和使用强大的新语言。