Kotlin 异常处理

文章目录

什么是异常

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

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

语法错误:

kt 复制代码
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

逻辑错误:

kt 复制代码
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 的类。

java 复制代码
public class Throwable implements Serializable {
	...
}

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

kt 复制代码
// 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

kt 复制代码
// 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将其抛出,后续代码将不再执行。

kt 复制代码
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 ...的内容,也需要特别留意,可以像搜索第一行一样搜索它。

kt 复制代码
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?类型。

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

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

kt 复制代码
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,将会捕获失败。

kt 复制代码
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

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

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

kt 复制代码
// 将 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类似:

kt 复制代码
fun main() {
    val msg = try {
        throw Throwable("抛出异常")
        "顺利执行"
    }catch (e: Throwable) {
        "出现异常"
    }
    print(msg)
}
出现异常
相关推荐
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
zhangphil15 小时前
Android Coil ImageLoader MemoryCache设置Key与复用内存缓存,Kotlin
android·kotlin
mmsx15 小时前
kotlin Java 使用ArrayList.add() ,set()前面所有值被 覆盖 的问题
android·开发语言·kotlin
lavins16 小时前
android studio kotlin项目build时候提示错误 Unknown Kotlin JVM target: 21
jvm·kotlin·android studio
面向未来_1 天前
JAVA Kotlin Androd 使用String.format()格式化日期
java·开发语言·kotlin
alexhilton1 天前
选择Retrofit还是Ktor:给Android开发者的指南
android·kotlin·android jetpack
GordonH19911 天前
Kotlin 优雅的接口实现
android·java·kotlin
wangz762 天前
Android 下用kotlin写一个sqlite
android·sqlite·kotlin·jetpack compose
yzpyzp2 天前
kotlin中RxHttp的toAwaitResponse和awaitResult函数的使用
android·kotlin
帅次2 天前
Flutter 异步编程利器:Future 与 Stream 深度解析
android·flutter·ios·小程序·kotlin·webview·android-studio