# Kotlin教程 - 11 异常
# 11.1 异常的概念
什么是异常?
异常(Exceptions)是指在程序执行过程中出现的错误或异常情况,导致程序无法继续正常执行的事件。
Kotlin 提供了异常处理机制来捕获和处理这些异常。在 Kotlin 中,异常处理和 Java 类似,但也有一些独特之处。在 Kotlin 中,所有异常都是非受检异常(unchecked exception),这意味着你不需要在函数上声明可能抛出的异常。
例如:整数除数是0会抛出异常。
fun main() {
var i: Int = 5 / 0
println(i)
println("异常后的代码")
}
2
3
4
5
6
执行的时候就会发生错误:
当程序出现错误的时候,我们通常称之为:抛出异常。
程序抛出异常就无法继续执行了,但是任何程序都不可能是完美的没有bug的,只能尽可能的对错误进行预防和处理。
# 11.2 捕获异常
对异常进行预防和提前处理,这种行为通常称之为异常捕获。
一个程序出现任何错误,就停止运行肯定不是我们希望看到的,即使程序出现错误,哪怕给与用户一个错误提示,也是一种积极的处理方式。
# 1 异常捕获语法
最简单的异常捕获语法:
try {
// 可能会抛出异常的代码
} catch (e: Exception) {
// 捕获并处理异常
}
2
3
4
5
举个栗子:
fun main() {
try {
var i: Int = 5 / 0
println(i)
} catch (e: Exception) {
println("除数不能为0")
}
println("异常后的代码")
}
2
3
4
5
6
7
8
9
10
上面的程序执行到 5 / 0
会抛出异常,出现异常后面的语句无法继续执行,然后执行 catch
块中的语句。
但是在 try-catch 代码块后面的代码可以继续执行。
执行结果:
除数不能为0 异常后的代码
这样在发生错误的时候,可以给用户一个提示。当然具体的处理方式需要根据业务需求的处理,这里只是举个例子。
# 2 打印异常详细信息
捕获异常后,在catch块中,可以通过异常对象打印异常的详细信息。
fun main() {
try {
var i: Int = 5 / 0
println(i)
} catch (e: Exception) {
println("除数不能为0")
e.printStackTrace()
}
println("异常后的代码")
}
2
3
4
5
6
7
8
9
10
11
执行结果:
# 3 捕获指定类型的异常
在程序运行时,可能会出现不同类型的异常,可能需要对不同类型的异常进行不同的处理,这个时候就需要根据类型来捕获异常了。
举个栗子:
fun main() {
try {
var i: Int = 5 / 0
println(i)
}
catch (e: ArithmeticException) {
println("除数不能为0")
e.printStackTrace()
}
println("异常后的代码")
}
2
3
4
5
6
7
8
9
10
11
12
上面捕获了 ArithmeticException
类型的异常,在捕获异常后,打印了异常信息和提示信息。
需要注意,上面只是捕获了 ArithmeticException 类型的异常,如果有代码抛出了其他类型的异常,是无法捕获的。
举个栗子,我们修改代码如下:
fun main() {
try {
var i: Int = 5 / 1
println(i)
var j = "abc".toInt()
println(j)
}
catch (e: ArithmeticException) {
println("除数不能为0")
e.printStackTrace()
}
println("异常后的代码")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
此时的代码会抛出 NumberFormatException
异常,但是我们并没有捕获这个异常,导致程序被终止执行。
所以针对这种情况需要捕获多个异常。
# 4 捕获多个异常
我们可以同时捕获多个异常,并对每种异常可以采用不同的处理。
举个栗子:
fun main() {
try {
var i: Int = 5 / 0
println(i)
var j = "abc".toInt()
println(j)
}
catch (e: ArithmeticException) {
println("除数不能为0")
e.printStackTrace()
}
catch (e: NumberFormatException) {
println("数字格式不正确")
e.printStackTrace()
}
catch (e: Exception) {
e.printStackTrace()
}
println("异常后的代码")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在上面的代码中,我们针对两种异常进行了针对性的处理,但是如果此时程序还抛出其他异常,就会导致程序终止执行。为了处理这种情况,我们在最后面捕获了 Exception
,它是所有异常的父类,这样如果有异常没有处理,就会被最后的 catch 捕获。
# 5 异常finally
finally表示的是无论是否发生异常都要执行的代码。
举个栗子:
fun main() {
try {
var i: Int = 5 / 0
println(i)
} catch (e: Exception) {
e.printStackTrace()
} finally {
println("出现异常也会被执行");
}
println("异常后的代码")
}
2
3
4
5
6
7
8
9
10
11
12
执行结果:
finally
一般在文件读写操作的时候用的比较多,就是不管是否发生错误都要关闭文件,所以可以将文件的关闭操作放在 finally
块中。
# 11.3 表达式风格的异常处理
在 Kotlin 中,try
表达式可以有返回值,因此你可以将其用作表达式。这使得异常处理更具表达性。
举个栗子:
fun main() {
val result = try {
5 / 0
} catch (e: Exception) {
0
} finally {
// 可选的 finally 块
}
println("Result: $result")
}
2
3
4
5
6
7
8
9
10
在上面的代码中,5 / 0
会抛出异常,则将返回 0 。如果 5 除以非 0,则返回计算的结果。
# 11.4 异常的传递
什么是异常的传递?
异常的传递,就是当函数或方法执行的时候出现异常,如果没有进行捕获,就会将异常传递给该函数或方法的调用者,如果调用者仍未处理异常,则继续向上传递,直到传递给主程序,如果主程序仍然没有进行异常处理,则程序将被终止。
举个栗子:
fun func() {
var i: Int = 5 / 0
println(i)
}
fun callFunc() {
// 调用上面的func()
func();
}
fun main() {
try {
callFunc();
} catch (e: Exception) {
e.printStackTrace()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上面代码,执行 5 / 0
会抛出异常,因为 func
函数中没有进行异常处理,则异常会抛给 callFunc()
函数,callFunc()
没有进行异常处理,则抛给 main()
函数,main()
函数中则对异常进行了处理,程序不会崩溃。
利用异常的传递性,如果我们在 main()
函数中进行了异常捕获,无论程序哪里发生了错误,最终都会被传递到 main()
函数中,保证所有的异常都会被捕获。但是不要所有的异常都在main()
函数中处理,main()
函数中的异常处理只是兜底处理。
# 11.5 主动抛出异常
除了代码执行的时候出错,系统会主动抛出异常,我们还可以根据实际的业务需要,主动抛出异常。
在 Kotlin 中就有一个内置函数 TODO()
该方法会主动抛出异常。
举个栗子:
fun sayHello() {
TODO("还没学会说话")
}
fun main() {
sayHello()
}
2
3
4
5
6
7
TODO()
方法的返回值是 Nothing
,抛出的是一个没有实现的异常。
执行结果:
如果我们要实现某个功能,但是还未实现,可以先使用 TODO()
方法来占位,代码运行到这里会报错,就知道还未实现。
我们也可以使用 throw 关键字抛出异常对象。
举个栗子:
fun inputUsername(): String {
print("请输入用户名:");
// 读取键盘输入
val name = readLine()
if (null == name || name.length > 16 || name.length < 8) {
throw Exception("用户名格式错误"); // 主动抛出异常
}
return name;
}
fun main() {
try {
var password = inputUsername();
print("输入的用户名:$password");
} catch (e: Exception) {
print("用户名格式错误,请重新输入");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
当输入abc的时候,执行结果:
请输入用户名:abc 用户名格式错误,请重新输入
# 11.6 自定义异常类
除了使用 Kotlin 内置的异常类型外,您还可以自定义异常类,以便更好地描述特定的异常情况。自定义异常类通常继承自 Exception
或其子类。建议以Exception结尾,见名知意。
举个栗子:
下面我们首先自定义了两个异常类,然后根据需要抛出这两个异常类的对象,在捕获异常的时候,根据异常类型进行不同的处理。
并在自定义异常类中可以封装自定义的参数。
/**
* 自定义一个用户名为空的异常
*/
class UsernameEmptyException : Exception("用户名为空")
/**
* 自定义一个用户名长度不正确的异常
*/
class UsernameLengthException(message: String, val minLength: Int, val maxLength: Int) : Exception(message) {
override fun toString(): String {
return "${this.message}, minLength:${this.minLength}, maxLength:${this.maxLength}"
}
}
fun inputUsername(): String {
print("请输入用户名:");
// 读取键盘输入
val name = readLine()
if (null == name || name.length < 1) {
throw UsernameEmptyException(); // 抛出用户名为空的异常
}
if (name.length > 16 || name.length < 8) {
throw UsernameLengthException("用户名格式错误", 8, 16); // 抛出用户名长度异常
}
return name;
}
fun main() {
try {
var password = inputUsername();
println("输入的用户名:$password");
} catch (e: UsernameEmptyException) {
println("用户名不能为空")
} catch (e: UsernameLengthException) {
println("用户名长度不正确")
println(e)
} catch (e: Exception) {
println("用户名格式错误,请重新输入");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
在上面的代码中,我们自定义了两个异常,并在代码中根据条件抛出了这两个异常。
然后在异常捕获的时候,可以针对这两个异常进行不同的处理。
# 11.7 先决条件函数
Kotlin 标准库中提供了一些内置函数,可以抛出自带信息的异常,这些便利函数叫做先决条件函数。
先决条件函数用于在执行某个操作之前验证一个条件。如果条件不满足,这些函数将引发 IllegalArgumentException
或 IllegalStateException
。这有助于在程序中更早地检测错误,并提供更有意义的错误消息。
以下是 Kotlin 中的主要先决条件函数:
# 1 require
fun main() {
val a:Int = 3
val b:Int = 1
require(b != 0) {"除数是0"}
val c = a / b
println(c)
}
2
3
4
5
6
7
8
require
函数用于检查传递给它的条件是否为 true
。如果条件为 false
,则会抛出 IllegalArgumentException
异常,同时可以提供一个可选的错误消息。
所以上面的代码,如果 b 不等于 0,代码正常执行,如果 b 等于 0,则 require 会抛出异常,执行结果如下:
# 2 check
fun validateInput(input: Int) {
check(input >= 0) { "input 必须大于等于0" }
// 继续处理输入
}
2
3
4
check
函数类似于 require
,也用于检查条件。如果条件为 false
,则会抛出 IllegalStateException
异常,同时可以提供一个可选的错误消息。
虽然 check
和 require
在功能上有一些相似之处,但在某些场景下,使用不同的异常类型有助于提供更具体的错误信息,从而更容易定位和调试问题。
# 3 assert
在 Kotlin 中,assert
函数已被设计为用于测试目的,而不是用于生产代码的错误检查。默认情况下,assert
在生产代码中是禁用的。你需要通过 -ea
(或 --enableassertions
)标志来启用它。运行代码的时候使用debug模式才会生效。
fun validateInput(input: Int) {
assert(input >= 0) { "Input 必须大于等于0" }
// 继续处理输入
}
2
3
4
# 4 requireNotNull
fun processNullableInput(input: String?) {
val nonNullInput = requireNotNull(input) { "input 不能为null" }
// 继续处理非空输入
}
2
3
4
requireNotNull
函数用于检查一个值是否为 null
,如果为空,它会抛出 IllegalArgumentException
异常,同时可以提供一个可选的错误消息。如果不为空,则将值返回。
这些先决条件函数是 Kotlin 中用于检查先决条件的常见方式,它们有助于在程序中及早发现错误,并提供有意义的错误消息。在实际应用中,它们通常用于参数验证或其他需要满足特定条件的地方。
← 10-泛型 12-Java互操作 →