Kotlin 异常处理

什么是异常

我们在运行程序时,如果代码出现了语法问题或逻辑问题,会导致程序编译失败或退出,称为异常。运行结果会给出一个一长串的红色字,通常会给出异常信息(异常名、调用堆栈等)。

语法错误会直接导致编译失败,不能被代码捕获;而逻辑异常一般是在运行时抛出的,可以捕获。

语法错误:

fun main() {
    print("Hello Kotlin"
}
// 异常信息(部分)
e: file:///D:/Project/Kotlin/Normal/untitled/src/main/kotlin/Main.kt:2:25 Expecting ')'

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction
   > Compilation error. See log for more details

逻辑错误:

fun main() {
    val list = listOf(1, 2, 3)

    print(list[3])
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at java.base/java.util.Arrays$ArrayList.get(Arrays.java:4266)
	at MainKt.main(Main.kt:4)
	at MainKt.main(Main.kt)

每一个异常都是一个Throwable的对象,这是一个 Java 的类。

public class Throwable implements Serializable {
	...
}

其实,Kotlin 中的异常来自于 Java 异常,并且使用typealias起了别名。

// Typealias.kt
package kotlin


@SinceKotlin("1.1") public actual typealias Error = java.lang.Error
@SinceKotlin("1.1") public actual typealias Exception = java.lang.Exception
...

Note:Kotlin 是支持跨平台的,并且有相应的分支,Kotlin/Jvm(桌面端、安卓Android)、Kotlin/Native(原生平台,如苹果 iOS)、Kotlin/JS(网页端 JavaScript)、Kotlin/Wasm(网页端 Assembly)。
Kotlin 跨平台项目有expectactual关键字,分别用于声明全平台期望内容(expect)以及各个平台的实现内容(actual)。以上的代码前方都标记了actual,说明这是平台代码,各个平台之间代码会有所差异,Jvm 平台有 Java,而其他平台没有。

从源码中可以发现,并没有Throwable的别名,Kotlin 创建了一个自己的Throwable对象。应该是为了跨平台兼容,其他平台没有 Java 也就没有java.lang.Throwable(其他平台应该是继承自kotlin.Throwable),在实际使用中我们用的是kotlin.Throwable

// Throwable.kt
package kotlin


public open class Throwable(open val message: String?, open val cause: Throwable?) {
    constructor(message: String?) : this(message, null)

    constructor(cause: Throwable?) : this(cause?.toString(), cause)

    constructor() : this(null, null)
}

抛出异常

我们可以使用throw抛出一个Throwable(不能是java.lang.Throwable),我们可以实例化Throwable或其子类,使用throw将其抛出,后续代码将不再执行。

fun main() {
    throw Throwable("你好,异常")
    print("Hello Kotlin")
}
Exception in thread "main" java.lang.Throwable: 你好,异常
	at MainKt.main(Main.kt:2)
	at MainKt.main(Main.kt)

通过异常信息解决异常

异常信息(红色字)是我们解决异常的关键。我们可以从异常信息中找到异常的名称以及抛出位置。

通常情况下,异常信息会非常长,异常信息的格式其实是固定的,异常名称、信息message值和抛出位置会在开头处,我们可以看一下下方的异常信息。

首先在开头处我们便可以得知在main这一线程(thread)中出现了一个异常(Exception in thread "main"),并且在后边跟着它的名称java.lang.Throwable,冒号:后面是异常的message值,如果messagenull,则不会有冒号及message的内容。

一般情况下,你只需要搜索第一行的信息,便会找到解决办法。当然,为了与Java 区分开,你可以在信息前加 Kotlin。

后边的就是调用堆栈,在这里可以找到异常抛出处,我们要注意那些蓝色字体(在 IDEA 中,Main.kt:2是蓝色的。Main.kt表示文件名;2表示行数,有时候会对不上),点击它光标会定位到该位置。越靠前的,就越靠近异常抛出位置。

有些时候,在中间位置也会出现类似Cause by ...的内容,也需要特别留意,可以像搜索第一行一样搜索它。

fun main() {
    throw Throwable("抛出异常")
}
Exception in thread "main" java.lang.Throwable: 抛出异常
	at MainKt.main(Main.kt:2)
	at MainKt.main(Main.kt)

捕获异常

有些情况下,我们并不希望异常直接抛出,例如,在安卓 Android 应用中,抛出异常将直接导致应用闪退,这是非常不好的体验。我们可以使用try-catch-finall来捕获异常。

finall可以缺省,我们先从简单的try-catch讲起。我们在try的花括号{}里可以写一段要执行的可能发生异常的代码,在后边补上catch,小括号()中需要写一个变量名称: Throwable 或其子类,表示你要捕获的异常,接着在catch花括号{}里写上捕获异常后要做什么,这里我们打印异常的消息,这是一个String?类型。

fun main() {
    try {
        // 这是一段要执行的代码
        println("try 1")
        throw Exception("异常消息")
        print("try 2")
    }catch (e: Exception) {
        // 如果上方代码抛出了异常
        // 则该地方可以尝试捕获异常
        print(e.message)
    }
}
try 1
异常消息

可以看到结果中没有异常了。如果我们去掉异常抛出语句,try中的内容会顺利执行,并且不会执行catch的内容:

fun main() {
    try {
        // 这是一段要执行的代码
        println("try 1")
        // throw Exception("异常消息")
        print("try 2")
    }catch (e: Exception) {
        // 如果上方代码抛出了异常
        // 则该地方可以尝试捕获异常
        print(e.message)
    }
}
try 1
try 2

这里需要注意,如果抛出的异常与catch圆括号中的类型不是同一类(不一定要类型相同,抛出异常也可以是圆括号中类型的子类),会捕获失败,抛出异常。这里举一个我一直以来都犯的错误,我会习惯性地把e的类型给成Exception,可是Throwable大致是分为两大类的:ExceptionError。我的声明e: Exception只对Exception及其子类生效,如果某一次它抛出了Error,将会捕获失败。

import java.awt.AWTError


fun main() {
    try {
        // AWTError: Error
        throw AWTError("AWT 错误")
    }catch (e: Exception) {
        print(e.message)
    }
}
Exception in thread "main" java.awt.AWTError: AWT 错误
	at MainKt.main(Main.kt:6)
	at MainKt.main(Main.kt)

所以建议大家,还是把它的类型写为Throwable

fun main() {
    try {
        // AWTError: Error
        throw AWTError("AWT 错误")
    }catch (e: Throwable) {
        print(e.message)
    }
}
AWT 错误

最后是finally,它不管是否抛出异常,都会执行相应的代码:

// 将 block 函数交给 catching 函数调用
inline fun catching(block: () -> Unit) {
    try {
        // 调用 block 函数
        block()
    } catch (_: Throwable) {
        // 空语句需要将 e 命名为 _
    } finally {
        println("Finally")
    }
}


fun main() {
    // 抛出异常
    catching { throw Throwable() }
    // 不抛出异常
    catching {  }
}
Finally
Finally

try-catch-finally还能用于赋值,这与ifwhen类似:

fun main() {
    val msg = try {
        throw Throwable("抛出异常")
        "顺利执行"
    }catch (e: Throwable) {
        "出现异常"
    }
    print(msg)
}
出现异常
  • 26
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值