异常(Exception) 异常能够让你的代码运行更加可预测, 即使发生可能中断程序执行的运行期错误. Kotlin 默认将所有异常看作 不受控的(unchecked)  异常. 不受控的异常简化了异常的处理过程: 你可以捕获异常, 但你不需要明确的处理或 声明  异常.
处理异常包括 2 个主要操作:
异常通过 Exception  类的子类来表示, Exception 是 Throwable  类的子类. 关于异常的层级结构, 详情请参见 异常的层级结构  小节. 由于 Exception 是一个 open 类 , 因此你可以创建 自定义异常 , 以满足你的 应用程序的特定需求.
抛出异常 你可以使用 throw 关键字, 手动抛出异常. 抛出一个异常, 表示在代码中发生了一个意外的运行期错误. 异常是 对象 , 抛出异常会创建一个异常类的一个实例.
你可以抛出一个没有任何参数的异常:
throw IllegalArgumentException()
为了更好的理解问题的根源, 请包含更多信息, 例如自定义消息, 以及原始原因:
val cause = IllegalStateException("Original cause: illegal state")
// 如果 userInput 为负数, 抛出一个 IllegalArgumentException 异常
// 此外, 它还显示原始原因, 通过 cause IllegalStateException 表示
if (userInput < 0) {
    throw IllegalArgumentException("Input must be non-negative", cause)
}
在这个示例中, 当使用者输入负数值时, 会抛出一个 IllegalArgumentException 异常. 你可以创建自定义的错误消息, 并保留异常的原始原因(cause), cause 会被包含在 栈追踪(stack trace)  中.
使用前提条件的检查函数抛出异常 Kotlin 提供了另一种方式, 使用前提条件的检查函数自动抛出异常. 前提条件的检查函数包括以下几种:
这些函数适合于, 如果指定的条件不满足, 程序的流程就无法继续的情况. 使用这些函数可以简化你的代码, 并让这些检查处理变得更加高效.
require() 函数 如果输入的参数对函数操作非常重要, 参数不正确函数就无法继续运行, 这样的情况下, 可以使用 require()  函数来校验输入参数.
如果 require() 中的条件不满足, 它会抛出一个 IllegalArgumentException  异常:
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))
    // 取消下面的行的注释, 查看一个能够运行的示例
    // println(getIndices(3))
    // 输出结果为: [1, 2, 3]
}
require() 函数允许编译器执行 智能类型转换 . 检查成功之后, 变量会自动转换为非 null 类型. 这些函数经常用来进行 null 检查, 在继续处理之前确保变量不为 null. 例如:
fun printNonNullString(str: String?) {
    // null 检查
    require(str != null) 
    // 检查成功之后, 'str' 可以确保不为 null,
    // 并被自动的智能类型转换为非 null 的 String 类型
    println(str.length)
}
check() 函数 可以使用 check()  函数来校验一个对象或变量的状态. 如果检查失败, 表示存在需要解决的逻辑错误.
如果 check() 函数中指定的条件为 false, 它会抛出一个 IllegalStateException  异常:
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"
    // 输出结果为 "non-empty-state"
    println(getStateValue())
}
check() 函数允许编译器执行 智能类型转换 . 检查成功之后, 变量会自动转换为非 null 类型. 这些函数经常用来进行 null 检查, 在继续处理之前确保变量不为 null. 例如:
fun printNonNullString(str: String?) {
    // null 检查
    check(str != null)
    // 检查成功之后, 'str' 可以确保不为 null,
    // 并被自动的智能类型转换为非 null 的 String 类型
    println(str.length)
}
error() 函数 error()  函数用来标记一个不合法的状态, 或代码中逻辑上不应该发生的条件. 它适合于你想要在你的代码中有意抛出一个异常的场景, 例如, 代码遇到了一个预料之外的状态. 这个函数在 when 表达式中特别有用, 它提供了一种清晰的方式, 处理逻辑上不应该发生的情况.
在下面的示例中, 使用了 error() 函数来处理一个未定义的用户角色. 如果用户角色不是预定义的角色之一, 就会抛出一个 IllegalStateException  异常:
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 代码段处理异常 当一个异常被抛出时, 它会中断程序的正常执行. 你可以使用 try 和 catch 关键字, 优雅的处理异常, 保持你的程序稳定. try 代码段包含可能抛出一个异常的代码, 如果异常发生, catch 代码段会捕获并处理异常. 异常会被与异常的类型, 或异常的 超类  匹配第一个 catch 代码段捕获.
你可以像下面这样, 共同使用 try 和 catch 关键字:
try {
    // 可能抛出一个异常的代码
} catch (e: SomeException) {
    // 处理异常的代码
}
将 try-catch 作为表达式使用, 是一种常见的方法, 这样就可以从 try 代码段或从 catch 代码段返回一个值:
fun main() {
    val num: Int = try {
        // 如果 count() 成功结束, 它的返回值会赋值给 num
        count()
    } catch (e: ArithmeticException) {
        // 如果 count() 抛出一个异常, catch 代码段返回 -1,
        // 然后赋值给 num
        -1
    }
    println("Result: $num")
}
// 模拟一个可能抛出 ArithmeticException 异常的函数
fun count(): Int {
    // 可以修改这个值, 返回不同的值给 num
    val a = 0
    return 10 / a
}
你可以对同一个 try 代码段使用多个 catch 处理块. 你可以根据需要添加任意数量的 catch 代码段, 分别处理不同的异常. 如果你使用多个 catch 代码段, 要将它们按照从最具体的异常到最不具体的异常的顺序, 在你的代码中排列为从上到下的顺序. 这个排列顺序与程序的执行流程相同.
我们来看看这个使用 自定义异常  的示例:
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 代码段, 你可以在 try 和 catch 代码段的执行之后清理代码. 在处理文件或网络连接这样的资源时, 这是非常重要的, 因为 finally 可以保证它们被正确的关闭或释放.
共同使用 try-catch-finally 代码段的方法通常如下:
try {
    // 可能抛出一个异常的代码
}
catch (e: YourException) {
    // 异常处理
}
finally {
    // 始终会执行的代码
}
try 表达式的返回值, 由 try 或 catch 代码段中最后执行的表达式决定. 如果没有异常发生, 结果来自 try 代码段; 如果一个异常被处理了, 结果就来自 catch 代码段. finally 代码段始终会被执行, 但它不会改变 try-catch 代码段的结果.
我们来看一个示例的演示:
fun divideOrNull(a: Int): Int {
    // try 代码段始终会被执行
    // 这里发生一个异常(被 0 除), 导致立即跳转到 catch 代码段
    try {
        val b = 44 / a
        println("try block: Executing division: $b")
        return b
    }
    // catch 代码段会被执行, 因为发生 ArithmeticException 异常 (当 a ==0 时, 会发生被 0 除的错误)
    catch (e: ArithmeticException) {
        println("catch block: Encountered ArithmeticException $e")
        return -1
    }
    finally {
        println("finally block: The finally block is always executed")
    }
}
fun main() {
    // 修改这个值, 可以得到不同的结果. ArithmeticException 异常会返回: -1
    divideOrNull(0)
}
在 Kotlin 中, 对于实现了 AutoClosable  接口的资源, 例如, FileInputStream 或 FileOutputStream 之类的文件流, 符合惯用法的管理方法是使用 .use()  函数. 这个函数会在代码段执行完毕后自动关闭资源, 无论代码段是否抛出异常, 因此不需要使用 finally 代码段. 所以, Kotlin 不需要 Java 的 try-with-resources  那样的特殊的语法来进行资源管理.
FileWriter("test.txt").use { writer ->
writer.write("some text") 
// 在这个代码段之后, .use 函数会自动调用 writer.close(), 与 finally 代码段类似
}
如果你的代码需要清理资源, 但不处理异常, 你也可以使用 try 和 finally 代码段, 但不使用 catch 代码段:
class MockResource {
    fun use() {
        println("Resource being used")
        // 模拟一个被使用的资源
        // 这里发生被 0 除错误, 抛出一个 ArithmeticException 异常
        val result = 100 / 0
        // 如果抛出异常, 这行不会执行
        println("Result: $result")
    }
    fun close() {
        println("Resource closed")
    }
}
fun main() {
    val resource = MockResource()
//sampleStart
    try {
        // 尝试使用资源
        resource.use()
    } finally {
        // 即使发生异常, 确保资源始终会被关闭
        resource.close()
    }
    // 如果抛出异常, 这行不会打印输出
    println("End of the program")
//sampleEnd
}
你可以看到, finally 代码段保证资源会被关闭, 无论是否有异常发生.
在 Kotlin 中, 你可以根据需求灵活的选择, 可以只使用 catch 代码段, 只使用 finally 代码段, 或者两者都使用, 但 try 代码段必须伴随至少一个 catch 代码段或 finally 代码段一起使用.
创建自定义异常 在 Kotlin 中, 你可以创建类, 扩展内建的 Exception 类, 定义自定义的异常. 通过这种方式, 你可以创建更加具体的错误类型, 以符合你的应用程序的需要.
要创建一个自定义的异常, 你可以定义一个类, 扩展 Exception 类:
class MyException: Exception("My message")
在这个示例中, 指定了默认的错误消息, "My message", 但如果你需要, 你可以不指定默认错误消息.
Kotlin 中的异常是有状态的对象, 带有与它们创建时的上下文环境相关的信息, 称为 栈追踪(stack trace) . 不要使用 对象声明  来创建异常. 相反, 要在每次需要时, 创建异常类的新实例. 通过这种方式, 你可以确保异常的状态准确的反映特定的上下文环境.
自定义异常也可以是任何既有的异常子类的子类, 例如子类 ArithmeticException:
class NumberTooLargeException: ArithmeticException("My message")
如果你想要创建自定义异常的子类, 你必须将父类声明为 open, 因为 类默认为 final , 不能声明子类.
例如:
// 将一个自定义异常声明为 open 类, 让它能够声明子类
open class MyCustomException(message: String): Exception(message)
// 创建自定义异常的子类
class SpecificCustomException: MyCustomException("Specific error message")
自定义异常的行为与内建的异常是一样的. 你可以使用 throw 关键字抛出自定义异常, 并使用 try-catch-finally 代码段处理它们. 我们来看一个示例的演示:
class NegativeNumberException: Exception("Parameter is less than zero.")
class NonNegativeNumberException: Exception("Parameter is a non-negative number.")
fun myFunction(number: Int) {
    if (number < 0) throw NegativeNumberException()
    else if (number >= 0) throw NonNegativeNumberException()
}
fun main() {
    // 修改函数中的这个值, 得到不同的异常
    myFunction(1)
}
在具有多种错误场景的应用程序中, 创建异常类的层级可以让代码更加清晰, 更加具体. 要做到这一点, 你可以使用一个 抽象类  或一个 封闭类  作为基类, 实现共通的异常功能, 并为详细的异常类型创建具体的子类. 此外, 带有可选参数的自定义异常提供一种灵活性, 能够使用不同的消息进行初始化, 实现更加精细的错误处理.
我们来看一个示例, 它使用封闭类 AccountException 作为异常类层级的基类, 以及子类 APIKeyExpiredException, 演示使用可选参数实现更高级的异常详细信息:
//sampleStart
// 创建一个封闭类, 作为账户相关错误的异常类层级的基类
sealed class AccountException(message: String, cause: Throwable? = null):
Exception(message, cause)
// 创建 AccountException 的一个子类
class InvalidAccountCredentialsException : AccountException("Invalid account credentials detected")
// 创建 AccountException 的一个子类, 能够指定自定义消息和错误原因
class APIKeyExpiredException(message: String = "API key expired", cause: Throwable? = null): AccountException(message, cause)
// 修改占位函数的值, 得到不同的结果
fun areCredentialsValid(): Boolean = true
fun isAPIKeyExpired(): Boolean = true
//sampleEnd
// 校验 account 证书 和 API key
fun validateAccount() {
    if (!areCredentialsValid()) throw InvalidAccountCredentialsException()
    if (isAPIKeyExpired()) {
        // 示例, 抛出 APIKeyExpiredException, 指定具体的原因
        val cause = RuntimeException("API key validation failed due to network error")
        throw APIKeyExpiredException(cause = cause)
    }
}
fun main() {
    try {
        validateAccount()
        println("Operation successful: Account credentials and API key are valid.")
    } catch (e: AccountException) {
        println("Error: ${e.message}")
        e.cause?.let { println("Caused by: ${it.message}") }
    }
}
Nothing 类型 在 Kotlin 中, 每个表达式都有类型. 表达式 throw IllegalArgumentException() 的类型是 Nothing , 这是一个内建类型, 它是所有其它类型的子类型, 也叫做 底类型(Bottom Type) . 也就是说, 在需要其它任何类型的地方, 都可以使用 Nothing 作为返回类型, 或泛型类型, 不会导致类型错误.
Nothing 是 Kotlin 中的一个特殊类型, 用来表示未能成功执行完毕的函数或表达式, 原因可能是它们总是抛出异常, 或者进入了无法终结的执行路径, 例如无限循环. 你可以使用 Nothing 来标记还没有实现的函数, 或者设计为总是抛出异常的函数, 向编译器, 也向代码的阅读者, 明确的表示你的意图. 如果编译器在函数签名中推断出 Nothing 类型, 它会提出警告. 将返回类型明确的定义为 Nothing, 可以消除这个警告.
这段 Kotlin 代码演示 Nothing 类型的使用, 这里编译器将函数调用之后的代码标记为不可到达:
class Person(val name: String?)
fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
    // 这个函数永远不会成功返回.
    // 它始终抛出一个异常.
}
fun main() {
    // 创建一个 Person 的实例, 'name' 为 null
    val person = Person(name = null)
    val s: String = person.name ?: fail("Name required")
    // 在这个地方, 's' 可以确保已被初始化
    println(s)
}
Kotlin 的 TODO()  函数, 也使用 Nothing 类型, 用作一个占位符, 用来突出表示未来需要实现的代码区域:
fun notImplementedFunction(): Int {
    TODO("This function is not yet implemented")
}
fun main() {
    val result = notImplementedFunction()
    // 这段代码抛出一个 NotImplementedError 异常
    println(result)
}
你可以看到, TODO() 函数永远会抛出一个 NotImplementedError  异常.
异常类 我们来看看 Kotlin 中的一些常见的异常类型, 它们都是 RuntimeException  类的子类:
ArithmeticException : 当一个算数操作无法执行时, 会发生这个异常, 例如被 0 除.
val example = 2 / 0 // 抛出 ArithmeticException 异常
IndexOutOfBoundsException : 抛出这个异常表示, 一个某种类型的下标, 例如一个数组或字符串下标, 超出了范围.
val myList = mutableListOf(1, 2, 3)
myList.removeAt(3)  // 抛出 IndexOutOfBoundsException 异常
要避免发生这个异常, 请使用更加安全的替代方案, 例如 getOrNull()  函数:
val myList = listOf(1, 2, 3)
// 返回 null, 而不是抛出 IndexOutOfBoundsException 异常
val element = myList.getOrNull(3)
println("Element at index 3: $element")
NoSuchElementException : 当一个元素在被访问的集合中不存在时, 会抛出这个异常. 这个错误发生在使用需要特定元素的方法的情况, 例如 first()  或 last() .
val emptyList = listOf<Int>()
val firstElement = emptyList.first()  // 抛出 NoSuchElementException 异常
要避免发生这个异常, 请使用更加安全的替代方案, 例如 firstOrNull()  函数:
val emptyList = listOf<Int>()
// 返回 null, 而不是抛出 NoSuchElementException 异常
val firstElement = emptyList.firstOrNull()
println("First element in empty list: $firstElement")
NumberFormatException : 当试图将一个字符串转换为数值类型, 但字符串格式不正确时, 会发生这个异常.
val string = "This is not a number"
val number = string.toInt() // 抛出 NumberFormatException 异常
要避免发生这个异常, 请使用更加安全的替代方案, 例如 toIntOrNull()  函数:
val nonNumericString = "not a number"
// 返回 null, 而不是抛出 NumberFormatException 异常
val number = nonNumericString.toIntOrNull()
println("Converted number: $number")
NullPointerException : 当一个应用程序尝试使用一个值为 null 的对象引用时, 会抛出这个异常. 尽管 Kotlin 的 null 安全性功能大大减少了发生 NullPointerException 的风险, 但仍然可能发生这个异常, 原因可能是有意的使用 !! 操作符, 或者与 Java 交互, 而 Java 缺乏 Kotlin 的 null 安全性.
val text: String? = null
println(text!!.length) // 抛出 a NullPointerException 异常
尽管 Kotlin 的所有异常都是不受控的(unchecked), 而且你不必明确的捕获异常, 但你仍然拥有灵活性, 可以在需要的时候捕获异常.
异常的层级结构 Kotlin 异常层级结构的根是 Throwable  类. 它有 2 个直接子类, Error  和 Exception :
Error 子类表示严重的基础性问题, 应用程序可能无法自行回复. 这些问题你通常不会尝试去处理, 例如 OutOfMemoryError  或 StackOverflowError.
Exception 子类用于你可能想要处理的条件. Exception 类型的子类, 例如 RuntimeException  和 IOException (输入/输出异常), 处理应用程序中的异常事件.
RuntimeException 通常由程序代码中的检查不足引起, 可以通过编程的方式预防. Kotlin 会帮助阻止常见的 RuntimeExceptions, 例如 NullPointerException, 并对潜在的运行期错误提供编译期警告, 例如, 被 0 除. 下图描述 RuntimeException 的子类型的层级结构:
栈追踪(stack trace) 栈追踪(stack trace)  由运行期环境生成的报告, 用于调试. 它显示导向程序中特定位置的函数调用序列, 尤其是错误异常发生的位置.
我们来看一个示例, 这里由于发生了 JVM 环境中的一个异常, 栈追踪(stack trace) 会自动打印输出:
fun main() {
//sampleStart
    throw ArithmeticException("This is an arithmetic exception!")
//sampleEnd
}
在 JVM 环境中运行这段代码, 会产生下面的输出:
Exception in thread "main" java.lang.ArithmeticException: This is an arithmetic exception!
    at MainKt.main(Main.kt:3)
    at MainKt.main(Main.kt)
第 1 行是异常的描述, 包括:
在异常描述之后, 以 at 开始的其它所有行, 是栈追踪(stack trace). 每一行称为一个 栈追踪元素(stack trace element)  或者叫一个 栈帧(stack frame) :
at MainKt.main (Main.kt:3): 这行显示方法名称 (MainKt.main), 以及调用这个方法的源代码文件和行号 (Main.kt:3).
at MainKt.main (Main.kt): 这行显示异常发生在 Main.kt 文件的 main() 函数内.
与 Java, Swift, 和 Objective-C 的异常互操作 由于 Kotlin 将所有异常当作不受控的(unchecked), 因此, 当从区分受控和不受控异常的语言中调用这些异常时, 可能导致复杂的情况. 为了解决 Kotlin 和 Java, Swift, 和 Objective-C 之类语言之间, 对异常处理的这种差异, 你可以使用 @Throws  注解. 这个注解会警告调用者可能出现的异常. 详情请参见 在 Java 中调用 Kotlin  和 与 Swift/Objective-C 交互 .
2025/10/21