异常
Kotlin
默认将所有异常视为未检查异常。未检查异常简化了异常处理过程:可以捕获异常,但不需要显式处理或声明它们。
异常由 Exception
类的子类表示,而 Exception
类是 Throwable
类的子类。由于 Exception
是一个开放类(open class
),您可以创建自定义异常以满足应用程序的特定需求。
抛出异常
您可以使用 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)
}
使用前置条件函数抛出异常
Kotlin
提供了额外的方式,通过前置条件函数自动抛出异常。前置条件函数包括:
前置条件函数 | 用例 | 抛出的异常 |
---|---|---|
require() | 检查用户输入的有效性 | IllegalArgumentException |
check() | 检查对象或变量状态的有效性 | IllegalStateException |
error() | 指示非法状态或条件 | IllegalStateException |
这些函数适用于在特定条件未满足时程序无法继续执行的情况。这种方式可以简化代码,并使处理这些检查更加高效。
使用 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))
}
require()
函数允许编译器执行智能转换。在检查成功后,变量会自动转换为非空类型。这些函数通常用于空值检查,以确保在继续操作之前变量不为空。例如:
fun printNonNullString(str: String?) {
require(str != null)
println(str.length)
}
使用 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"
println(getStateValue()) // non-empty-state
}
check()
函数允许编译器执行智能转换。在检查成功后,变量会自动转换为非空类型。这些函数通常用于空值检查,以确保在继续操作之前变量不为空。例如:
fun printNonNullString(str: String?) {
check(str != null)
println(str.length)
}
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 (e: SomeException) {
// 处理异常的代码。
}
一种常见的方法是将 try-catch
用作表达式,这样它可以从 try
块或 catch
块返回值:
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
块时,重要的是按照从最具体到最不具体异常的顺序排列它们,遵循代码中的从上到下的顺序。这种排序与程序的执行流程一致。
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
可以确保它们被正确关闭或释放。
// error
// finally
fun main() {
try {
error("error")
}
catch (e: Exception) {
println(e.message)
}
finally {
println("finally")
}
}
// finally
fun main() {
try {
}
catch (e: Exception) {
println(e.message)
}
finally {
println("finally")
}
}
try
表达式的返回值由 try
或 catch
块中最后执行的表达式决定。如果没有发生异常,结果来自 try
块;如果处理了异常,则结果来自 catch
块。finally
块总是会执行,但它不会改变 try-catch
块的结果。
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
接口的资源(例如 FileInputStream
或 FileOutputStream
等文件流)的惯用方法是使用 use
函数。该函数在代码块执行完成后自动关闭资源,无论是否抛出异常,从而无需使用 finally
块。
FileWriter("test.txt").use { writer ->
writer.write("some text")
// 在这个代码块之后,use 函数会自动调用 writer.close(),类似于 finally 块的作用。
}
如果您的代码需要进行资源清理而不处理异常,您也可以使用 try
和 finally
块,而不使用 catch
块:
try {
resource.use()
} finally {
resource.close()
}
如您所见,finally
块保证了无论是否发生异常,资源都会被关闭。
在 Kotlin
中,您可以根据具体需求灵活地仅使用 catch
块、仅使用 finally
块,或者两者都使用,但 try
块必须始终至少伴随一个 catch
块或 finally
块。