Kotlin 2.1.0 入门教程(十二)异常

异常

Kotlin 默认将所有异常视为未检查异常。未检查异常简化了异常处理过程:可以捕获异常,但不需要显式处理或声明它们。

异常由 Exception 类的子类表示,而 Exception 类是 Throwable 类的子类。由于 Exception 是一个开放类(open class),您可以创建自定义异常以满足应用程序的特定需求。

抛出异常

您可以使用 throw 关键字手动抛出异常。抛出异常表示代码中发生了意外的运行时错误。异常是对象,抛出异常时会创建异常类的一个实例。

您可以抛出一个不带任何参数的异常:

kotlin 复制代码
throw IllegalArgumentException()

为了更好地理解问题的根源,可以包含额外的信息,例如自定义消息和原始原因:

kotlin 复制代码
val cause = IllegalStateException("Original cause: illegal state")

// 如果 userInput 为负数,则抛出 IllegalArgumentException 异常。
// 同时显示原始原因,由 cause IllegalStateException 表示。
if (userInput < 0) {
    throw IllegalArgumentException("Input must be non-negative", cause)
}

使用前置条件函数抛出异常

Kotlin 提供了额外的方式,通过前置条件函数自动抛出异常。前置条件函数包括:

前置条件函数 用例 抛出的异常
require() 检查用户输入的有效性 IllegalArgumentException
check() 检查对象或变量状态的有效性 IllegalStateException
error() 指示非法状态或条件 IllegalStateException

这些函数适用于在特定条件未满足时程序无法继续执行的情况。这种方式可以简化代码,并使处理这些检查更加高效。

使用 require() 函数来验证输入参数,当这些参数对函数的操作至关重要且如果参数无效时函数无法继续执行时。如果 require() 中的条件未满足,它会抛出 IllegalArgumentException 异常:

kotlin 复制代码
fun getIndices(count: Int): List<Int> {
    require(count >= 0) { "Count must be non-negative. You set count to $count." }
    return List(count) { it + 1 }
}

fun main() {
    // 这将失败并抛出 IllegalArgumentException 异常。
    println(getIndices(-1))
}

require() 函数允许编译器执行智能转换。在检查成功后,变量会自动转换为非空类型。这些函数通常用于空值检查,以确保在继续操作之前变量不为空。例如:

kotlin 复制代码
fun printNonNullString(str: String?) {
    require(str != null)
    println(str.length)
}

使用 check() 函数来验证对象或变量的状态。如果检查失败,则表明存在需要解决的逻辑错误。如果 check() 函数中指定的条件为 false,它会抛出 IllegalStateException 异常:

kotlin 复制代码
fun main() {
    var someState: String? = null

    fun getStateValue(): String {
        val state = checkNotNull(someState) { "State must be set beforehand!" }
        check(state.isNotEmpty()) { "State must be non-empty!" }
        return state
    }

    // 如果您取消注释下面行,程序将因 IllegalStateException 异常而失败。
    // getStateValue()

    someState = ""

    // 如果您取消注释下面行,程序将因 IllegalStateException 异常而失败。
    // getStateValue()

    someState = "non-empty-state"

    println(getStateValue()) // non-empty-state
}

check() 函数允许编译器执行智能转换。在检查成功后,变量会自动转换为非空类型。这些函数通常用于空值检查,以确保在继续操作之前变量不为空。例如:

kotlin 复制代码
fun printNonNullString(str: String?) {
    check(str != null)
    println(str.length)
}

error() 函数用于表示代码中的非法状态或逻辑上不应发生的条件。它适用于您希望在代码中故意抛出异常的场景,例如当代码遇到意外状态时。此函数在 when 表达式中特别有用,为处理逻辑上不应发生的情况提供了一种清晰的方式。

在以下示例中,error() 函数用于处理未定义的用户角色。如果角色不是预定义的角色之一,则会抛出 IllegalStateException 异常:

kotlin 复制代码
class User(val name: String, val role: String)

fun processUserRole(user: User) {
    when (user.role) {
        "admin" -> println("${user.name} is an admin.")
        "editor" -> println("${user.name} is an editor.")
        "viewer" -> println("${user.name} is a viewer.")
        else -> error("Undefined role: ${user.role}")
    }
}

fun main() {
    val user1 = User("Alice", "admin")
    processUserRole(user1) // Alice is an admin.

    // 抛出 IllegalStateException 异常。
    val user2 = User("Bob", "guest")
    processUserRole(user2)
}

使用 try-catch 块处理异常

当抛出异常时,它会中断程序的正常执行。您可以使用 trycatch 关键字优雅地处理异常,以保持程序的稳定性。try 块包含可能抛出异常的代码,而 catch 块捕获并处理发生的异常。异常会被第一个匹配其特定类型或异常超类的 catch 块捕获。

kotlin 复制代码
try {
    // 可能抛出异常的代码。
} catch (e: SomeException) {
    // 处理异常的代码。
}

一种常见的方法是将 try-catch 用作表达式,这样它可以从 try 块或 catch 块返回值:

kotlin 复制代码
fun main() {
    val num: Int = try {
        // 如果 count() 成功执行,其返回值将赋给 num。
        count()
    } catch (e: ArithmeticException) {
        // 如果 count() 抛出异常,catch 块返回 -1,并将其赋给 num。
        -1
    }
}

fun count(): Int {
    return 10 / 0
}

您可以为同一个 try 块使用多个 catch 处理程序。可以根据需要添加任意数量的 catch 块,以区分处理不同的异常。当有多个 catch 块时,重要的是按照从最具体到最不具体异常的顺序排列它们,遵循代码中的从上到下的顺序。这种排序与程序的执行流程一致。

kotlin 复制代码
open class WithdrawalException(message: String) : Exception(message)
class InsufficientFundsException(message: String) : WithdrawalException(message)

fun processWithdrawal(amount: Double, availableFunds: Double) {
    if (amount > availableFunds) {
        throw InsufficientFundsException("Insufficient funds for the withdrawal.")
    }
    if (amount < 1 || amount % 1 != 0.0) {
        throw WithdrawalException("Invalid withdrawal amount.")
    }
    println("Withdrawal processed")
}

fun main() {
    val availableFunds = 500.0
    val withdrawalAmount = 500.5

    try {
        processWithdrawal(withdrawalAmount.toDouble(), availableFunds)
    } catch (e: InsufficientFundsException) {
        println("Caught an InsufficientFundsException: ${e.message}")
    } catch (e: WithdrawalException) {
        println("Caught a WithdrawalException: ${e.message}")
    }
}

一个处理 WithdrawalException 的通用 catch 块会捕获其类型的所有异常,包括像 InsufficientFundsException 这样的特定异常,除非它们被更具体的 catch 块提前捕获。

finally

finally 块包含的代码无论 try 块是成功完成还是抛出异常都会执行。通过 finally 块,您可以在 trycatch 块执行后进行清理操作。这在处理文件或网络连接等资源时尤为重要,因为 finally 可以确保它们被正确关闭或释放。

kotlin 复制代码
// error
// finally
fun main() {
    try {
        error("error")
    }
    catch (e: Exception) {
        println(e.message)
    }
    finally {
        println("finally")
    }    
}
kotlin 复制代码
// finally
fun main() {
    try {
    }
    catch (e: Exception) {
        println(e.message)
    }
    finally {
        println("finally")
    }    
}

try 表达式的返回值由 trycatch 块中最后执行的表达式决定。如果没有发生异常,结果来自 try 块;如果处理了异常,则结果来自 catch 块。finally 块总是会执行,但它不会改变 try-catch 块的结果。

kotlin 复制代码
fun divideOrNull(a: Int): Int {
    try {
        val b = 44 / a
        println("try block: Executing division: $b")
        return b
    }
    catch (e: ArithmeticException) {
        println("catch block: Encountered ArithmeticException $e")
        return -1
    }
    finally {
        println("finally block: The finally block is always executed")
    }
}

fun main() {
    // catch block: Encountered ArithmeticException java.lang.ArithmeticException: / by zero
    // finally block: The finally block is always executed
    // -1
    println(divideOrNull(0))
    
    // try block: Executing division: 44
    // finally block: The finally block is always executed
    // 44
    println(divideOrNull(1))
}

Kotlin 中,管理实现了 AutoClosable 接口的资源(例如 FileInputStreamFileOutputStream 等文件流)的惯用方法是使用 use 函数。该函数在代码块执行完成后自动关闭资源,无论是否抛出异常,从而无需使用 finally 块。

kotlin 复制代码
FileWriter("test.txt").use { writer ->
    writer.write("some text")
    // 在这个代码块之后,use 函数会自动调用 writer.close(),类似于 finally 块的作用。
}

如果您的代码需要进行资源清理而不处理异常,您也可以使用 tryfinally 块,而不使用 catch 块:

kotlin 复制代码
try {
    resource.use()
} finally {
    resource.close()
}

如您所见,finally 块保证了无论是否发生异常,资源都会被关闭。

Kotlin 中,您可以根据具体需求灵活地仅使用 catch 块、仅使用 finally 块,或者两者都使用,但 try 块必须始终至少伴随一个 catch 块或 finally 块。

相关推荐
货拉拉技术3 小时前
记一次无障碍测试引发app崩溃问题的排查与解决
android·前端·程序员
GrimRaider3 小时前
【逆向工程】破解unity的安卓apk包
android·unity·游戏引擎·软件逆向
yzpyzp3 小时前
Jetpack之ViewBinding和DataBinding的区别
android
小墙程序员4 小时前
一文了解Android的build目录结构
android·gradle
叶落方知秋4 小时前
OkHttp 3.10.0版本源码之重试重定向拦截器原理分析
android·前端框架
无聊的烤苕皮4 小时前
MySQL第五次作业(触发器、存储过程)
android·mysql·adb
ianozo5 小时前
BUU34 [BSidesCF 2020]Had a bad day1 【php://filter】
android
xvch5 小时前
Kotlin 2.1.0 入门教程(十三)异常、Nothing
android·kotlin
深色風信子5 小时前
Kotlin Bytedeco OpenCV 图像图像51.1 KNN背景消除
opencv·kotlin·bytedeco·knn背景消除·背景消除
陈老师还在写代码6 小时前
安卓开发用Java、Flutter、Kotlin的区别
android·java·flutter