异常处理
概述
异常处理是程序健壮性的重要保障。Kotlin提供了完善的异常处理机制,包括try-catch语句、自定义异常、Result类型等。本章将详细介绍如何在Kotlin中优雅地处理错误和异常情况。
异常基础
try-catch-finally 语句
kotlin
import java.io.File
import java.io.FileNotFoundException
fun main() {
// 基本异常处理
fun divide(a: Int, b: Int): Double {
return try {
a.toDouble() / b
} catch (e: ArithmeticException) {
println("算术异常:${e.message}")
0.0
}
}
println("10 / 2 = ${divide(10, 2)}")
println("10 / 0 = ${divide(10, 0)}")
// 多个catch块
fun parseNumber(str: String): Int? {
return try {
str.toInt()
} catch (e: NumberFormatException) {
println("数字格式错误:$str")
null
} catch (e: Exception) {
println("其他异常:${e.message}")
null
}
}
println("解析 '123': ${parseNumber("123")}")
println("解析 'abc': ${parseNumber("abc")}")
// try-catch-finally
fun readFileContent(filename: String): String? {
var content: String? = null
try {
val file = File(filename)
content = file.readText()
println("文件读取成功")
} catch (e: FileNotFoundException) {
println("文件未找到:$filename")
} catch (e: Exception) {
println("读取文件时发生错误:${e.message}")
} finally {
println("清理资源")
}
return content
}
readFileContent("nonexistent.txt")
}try 作为表达式
kotlin
fun main() {
// try作为表达式
fun safeParseInt(str: String): Int {
val result = try {
str.toInt()
} catch (e: NumberFormatException) {
-1 // 默认值
}
return result
}
println("解析 '42': ${safeParseInt("42")}")
println("解析 'invalid': ${safeParseInt("invalid")}")
// 复杂的try表达式
fun processData(data: String): String {
return try {
val number = data.toInt()
when {
number > 0 -> "正数:$number"
number < 0 -> "负数:$number"
else -> "零"
}
} catch (e: NumberFormatException) {
try {
val double = data.toDouble()
"浮点数:$double"
} catch (e: NumberFormatException) {
"无效数据:$data"
}
}
}
println(processData("42"))
println(processData("-10"))
println(processData("3.14"))
println(processData("hello"))
}异常类型
标准异常
kotlin
fun main() {
// 常见的标准异常
// NullPointerException (KotlinNullPointerException)
fun demonstrateNPE() {
val str: String? = null
try {
println(str!!.length) // 强制解包null值
} catch (e: KotlinNullPointerException) {
println("空指针异常:${e.message}")
}
}
// IndexOutOfBoundsException
fun demonstrateIndexOutOfBounds() {
val list = listOf(1, 2, 3)
try {
println(list[5])
} catch (e: IndexOutOfBoundsException) {
println("索引越界:${e.message}")
}
}
// IllegalArgumentException
fun validateAge(age: Int) {
if (age < 0 || age > 150) {
throw IllegalArgumentException("年龄必须在0-150之间,实际值:$age")
}
println("年龄验证通过:$age")
}
// IllegalStateException
class BankAccount(private var balance: Double) {
fun withdraw(amount: Double) {
if (balance < amount) {
throw IllegalStateException("余额不足,当前余额:$balance,尝试提取:$amount")
}
balance -= amount
println("提取成功,余额:$balance")
}
}
println("=== 异常演示 ===")
demonstrateNPE()
demonstrateIndexOutOfBounds()
try {
validateAge(-5)
} catch (e: IllegalArgumentException) {
println("参数异常:${e.message}")
}
val account = BankAccount(100.0)
try {
account.withdraw(150.0)
} catch (e: IllegalStateException) {
println("状态异常:${e.message}")
}
}自定义异常
kotlin
// 自定义异常类
class ValidationException(message: String, cause: Throwable? = null) : Exception(message, cause)
class NetworkException(message: String, val errorCode: Int) : Exception(message)
class BusinessLogicException(
message: String,
val errorType: ErrorType,
val details: Map<String, Any> = emptyMap()
) : Exception(message)
enum class ErrorType {
INVALID_INPUT,
RESOURCE_NOT_FOUND,
PERMISSION_DENIED,
BUSINESS_RULE_VIOLATION
}
// 使用自定义异常的服务类
class UserService {
private val users = mutableMapOf<String, User>()
data class User(val id: String, val name: String, val email: String, val age: Int)
fun createUser(id: String, name: String, email: String, age: Int): User {
// 验证输入
if (id.isBlank()) {
throw ValidationException("用户ID不能为空")
}
if (name.isBlank()) {
throw ValidationException("用户名不能为空")
}
if (!email.contains("@")) {
throw ValidationException("邮箱格式无效:$email")
}
if (age < 0 || age > 150) {
throw ValidationException("年龄必须在0-150之间:$age")
}
// 检查业务规则
if (users.containsKey(id)) {
throw BusinessLogicException(
"用户ID已存在",
ErrorType.BUSINESS_RULE_VIOLATION,
mapOf("existingUserId" to id)
)
}
val user = User(id, name, email, age)
users[id] = user
return user
}
fun getUser(id: String): User {
return users[id] ?: throw BusinessLogicException(
"用户不存在",
ErrorType.RESOURCE_NOT_FOUND,
mapOf("requestedUserId" to id)
)
}
fun updateUserEmail(id: String, newEmail: String): User {
val user = getUser(id) // 可能抛出异常
if (!newEmail.contains("@")) {
throw ValidationException("邮箱格式无效:$newEmail")
}
val updatedUser = user.copy(email = newEmail)
users[id] = updatedUser
return updatedUser
}
}
fun main() {
val userService = UserService()
// 成功创建用户
try {
val user1 = userService.createUser("1", "Alice", "alice@example.com", 25)
println("用户创建成功:$user1")
} catch (e: ValidationException) {
println("验证失败:${e.message}")
} catch (e: BusinessLogicException) {
println("业务逻辑错误:${e.message},类型:${e.errorType}")
}
// 验证异常
try {
userService.createUser("", "Bob", "invalid-email", -5)
} catch (e: ValidationException) {
println("验证失败:${e.message}")
}
// 业务逻辑异常
try {
userService.createUser("1", "Charlie", "charlie@example.com", 30)
} catch (e: BusinessLogicException) {
println("业务逻辑错误:${e.message}")
println("错误详情:${e.details}")
}
// 资源未找到异常
try {
userService.getUser("999")
} catch (e: BusinessLogicException) {
println("${e.errorType}: ${e.message}")
}
}Result 类型
使用 Result 进行错误处理
kotlin
// 使用Result类型的安全操作
class SafeCalculator {
fun divide(a: Double, b: Double): Result<Double> {
return if (b != 0.0) {
Result.success(a / b)
} else {
Result.failure(ArithmeticException("除数不能为零"))
}
}
fun sqrt(x: Double): Result<Double> {
return if (x >= 0) {
Result.success(kotlin.math.sqrt(x))
} else {
Result.failure(IllegalArgumentException("不能计算负数的平方根"))
}
}
fun factorial(n: Int): Result<Long> {
return when {
n < 0 -> Result.failure(IllegalArgumentException("阶乘的参数不能为负数"))
n > 20 -> Result.failure(ArithmeticException("阶乘结果过大,超出Long范围"))
else -> {
var result = 1L
for (i in 1..n) {
result *= i
}
Result.success(result)
}
}
}
}
// Result的链式操作
class DataProcessor {
fun parseNumber(str: String): Result<Double> {
return try {
Result.success(str.toDouble())
} catch (e: NumberFormatException) {
Result.failure(e)
}
}
fun processChain(input: String): Result<String> {
return parseNumber(input)
.mapCatching { number ->
if (number < 0) throw IllegalArgumentException("数字不能为负数")
number
}
.mapCatching { number ->
kotlin.math.sqrt(number)
}
.mapCatching { sqrt ->
"平方根结果:${"%.2f".format(sqrt)}"
}
}
}
fun main() {
val calculator = SafeCalculator()
val processor = DataProcessor()
println("=== Result类型示例 ===")
// 成功的计算
val divisionResult = calculator.divide(10.0, 2.0)
divisionResult.fold(
onSuccess = { result -> println("除法结果:$result") },
onFailure = { error -> println("除法错误:${error.message}") }
)
// 失败的计算
val divisionByZero = calculator.divide(10.0, 0.0)
when {
divisionByZero.isSuccess -> println("结果:${divisionByZero.getOrNull()}")
divisionByZero.isFailure -> println("错误:${divisionByZero.exceptionOrNull()?.message}")
}
// 平方根计算
listOf(16.0, -4.0, 25.0).forEach { number ->
calculator.sqrt(number).fold(
onSuccess = { result -> println("√$number = $result") },
onFailure = { error -> println("√$number 错误:${error.message}") }
)
}
// 阶乘计算
listOf(5, -3, 25).forEach { number ->
val result = calculator.factorial(number)
println("$number! = ${result.getOrElse { "错误:${it.message}" }}")
}
println("\n=== 链式操作示例 ===")
// 链式处理
listOf("16", "-4", "abc", "25").forEach { input ->
val result = processor.processChain(input)
println("输入 '$input': ${result.getOrElse { "错误:${it.message}" }}")
}
}Result 的高级用法
kotlin
// 扩展函数增强Result功能
inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> {
return when {
isSuccess -> transform(getOrThrow())
else -> Result.failure(exceptionOrNull()!!)
}
}
fun <T> Result<T>.orElse(defaultValue: T): T {
return getOrElse { defaultValue }
}
fun <T> Result<T>.orElseGet(supplier: () -> T): T {
return getOrElse { supplier() }
}
// 批量操作Result
fun <T> List<Result<T>>.sequence(): Result<List<T>> {
val results = mutableListOf<T>()
for (result in this) {
when {
result.isSuccess -> results.add(result.getOrThrow())
result.isFailure -> return Result.failure(result.exceptionOrNull()!!)
}
}
return Result.success(results)
}
// 实际应用:文件处理服务
class FileProcessingService {
fun readFile(filename: String): Result<String> {
return try {
// 模拟文件读取
when (filename) {
"config.txt" -> Result.success("app.name=MyApp\napp.version=1.0")
"data.json" -> Result.success("""{"users": [{"name": "Alice"}, {"name": "Bob"}]}""")
else -> Result.failure(java.io.FileNotFoundException("文件不存在:$filename"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
fun parseConfig(content: String): Result<Map<String, String>> {
return try {
val config = content.lines()
.filter { it.contains("=") }
.associate { line ->
val parts = line.split("=", limit = 2)
parts[0].trim() to parts[1].trim()
}
Result.success(config)
} catch (e: Exception) {
Result.failure(IllegalArgumentException("配置格式错误", e))
}
}
fun validateConfig(config: Map<String, String>): Result<Map<String, String>> {
return when {
!config.containsKey("app.name") ->
Result.failure(ValidationException("缺少必需的配置项:app.name"))
!config.containsKey("app.version") ->
Result.failure(ValidationException("缺少必需的配置项:app.version"))
config["app.name"]?.isBlank() == true ->
Result.failure(ValidationException("app.name不能为空"))
else -> Result.success(config)
}
}
fun processConfigFile(filename: String): Result<Map<String, String>> {
return readFile(filename)
.flatMap { content -> parseConfig(content) }
.flatMap { config -> validateConfig(config) }
}
}
fun main() {
val fileService = FileProcessingService()
println("=== 文件处理服务示例 ===")
// 处理不同的文件
listOf("config.txt", "data.json", "nonexistent.txt").forEach { filename ->
println("\n处理文件:$filename")
val result = fileService.processConfigFile(filename)
result.fold(
onSuccess = { config ->
println("配置加载成功:")
config.forEach { (key, value) -> println(" $key = $value") }
},
onFailure = { error ->
println("处理失败:${error.message}")
error.cause?.let { cause ->
println("原因:${cause.message}")
}
}
)
}
println("\n=== 批量操作示例 ===")
// 批量读取文件
val filenames = listOf("config.txt", "data.json", "missing.txt")
val readResults = filenames.map { fileService.readFile(it) }
// 检查是否所有文件都读取成功
val allFilesResult = readResults.sequence()
allFilesResult.fold(
onSuccess = { contents ->
println("所有文件读取成功:")
contents.forEachIndexed { index, content ->
println("${filenames[index]}: ${content.take(50)}...")
}
},
onFailure = { error ->
println("批量读取失败:${error.message}")
}
)
// 只处理成功的结果
val successfulReads = readResults.mapNotNull { it.getOrNull() }
println("成功读取的文件数量:${successfulReads.size}")
}异常处理最佳实践
1. 异常处理策略
kotlin
// 异常处理策略示例
class OrderService {
// 快速失败策略
fun validateOrder(order: Order) {
require(order.items.isNotEmpty()) { "订单不能为空" }
require(order.customerId.isNotBlank()) { "客户ID不能为空" }
require(order.totalAmount > 0) { "订单金额必须大于0" }
}
// 优雅降级策略
fun calculateShippingCost(order: Order): Double {
return try {
// 调用外部服务计算运费
callExternalShippingService(order)
} catch (e: NetworkException) {
// 网络异常时使用默认运费
println("运费服务不可用,使用默认运费")
10.0
} catch (e: Exception) {
// 其他异常时免运费
println("运费计算失败,免运费处理")
0.0
}
}
// 重试策略
fun processPayment(order: Order, maxRetries: Int = 3): Result<PaymentResult> {
var lastException: Exception? = null
repeat(maxRetries) { attempt ->
try {
val result = callPaymentService(order)
return Result.success(result)
} catch (e: NetworkException) {
lastException = e
println("支付尝试 ${attempt + 1} 失败,${maxRetries - attempt - 1} 次重试机会")
if (attempt < maxRetries - 1) {
Thread.sleep(1000 * (attempt + 1)) // 指数退避
}
} catch (e: Exception) {
// 非网络异常不重试
return Result.failure(e)
}
}
return Result.failure(lastException ?: Exception("支付处理失败"))
}
private fun callExternalShippingService(order: Order): Double {
// 模拟外部服务调用
if (kotlin.random.Random.nextBoolean()) {
throw NetworkException("运费服务连接超时", 408)
}
return order.totalAmount * 0.1
}
private fun callPaymentService(order: Order): PaymentResult {
// 模拟支付服务调用
val random = kotlin.random.Random.nextDouble()
when {
random < 0.1 -> throw NetworkException("支付服务不可用", 503)
random < 0.2 -> throw Exception("支付被拒绝")
else -> return PaymentResult("SUCCESS", "TXN_${System.currentTimeMillis()}")
}
}
}
data class Order(
val id: String,
val customerId: String,
val items: List<OrderItem>,
val totalAmount: Double
)
data class OrderItem(val productId: String, val quantity: Int, val price: Double)
data class PaymentResult(val status: String, val transactionId: String)
fun main() {
val orderService = OrderService()
val order = Order(
id = "ORD001",
customerId = "CUST001",
items = listOf(
OrderItem("PROD001", 2, 25.0),
OrderItem("PROD002", 1, 50.0)
),
totalAmount = 100.0
)
// 验证订单
try {
orderService.validateOrder(order)
println("订单验证通过")
} catch (e: IllegalArgumentException) {
println("订单验证失败:${e.message}")
return
}
// 计算运费(优雅降级)
val shippingCost = orderService.calculateShippingCost(order)
println("运费:$shippingCost")
// 处理支付(重试策略)
val paymentResult = orderService.processPayment(order)
paymentResult.fold(
onSuccess = { result ->
println("支付成功:${result.transactionId}")
},
onFailure = { error ->
println("支付失败:${error.message}")
}
)
}2. 资源管理
kotlin
import java.io.Closeable
// 自动资源管理
inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
this == null -> {}
exception == null -> close()
else -> {
try {
close()
} catch (closeException: Throwable) {
exception.addSuppressed(closeException)
}
}
}
}
}
// 资源管理示例
class DatabaseConnection : Closeable {
private var isOpen = true
fun executeQuery(sql: String): List<Map<String, Any>> {
if (!isOpen) throw IllegalStateException("连接已关闭")
println("执行查询:$sql")
// 模拟查询结果
return listOf(
mapOf("id" to 1, "name" to "Alice"),
mapOf("id" to 2, "name" to "Bob")
)
}
override fun close() {
if (isOpen) {
println("关闭数据库连接")
isOpen = false
}
}
}
class FileWriter(private val filename: String) : Closeable {
private var isOpen = true
private val content = mutableListOf<String>()
fun writeLine(line: String) {
if (!isOpen) throw IllegalStateException("文件已关闭")
content.add(line)
println("写入:$line")
}
override fun close() {
if (isOpen) {
println("保存文件:$filename")
println("内容:${content.joinToString("\n")}")
isOpen = false
}
}
}
fun main() {
println("=== 资源管理示例 ===")
// 数据库连接自动管理
try {
DatabaseConnection().use { connection ->
val results = connection.executeQuery("SELECT * FROM users")
println("查询结果:$results")
// 如果这里抛出异常,连接仍会被正确关闭
if (results.isEmpty()) {
throw RuntimeException("没有找到数据")
}
}
} catch (e: Exception) {
println("数据库操作异常:${e.message}")
}
// 文件写入自动管理
try {
FileWriter("output.txt").use { writer ->
writer.writeLine("第一行")
writer.writeLine("第二行")
// 模拟异常
if (kotlin.random.Random.nextBoolean()) {
throw RuntimeException("写入过程中发生错误")
}
writer.writeLine("第三行")
}
} catch (e: Exception) {
println("文件写入异常:${e.message}")
}
println("程序结束")
}3. 错误传播和转换
kotlin
// 错误传播和转换示例
sealed class AppError(message: String, cause: Throwable? = null) : Exception(message, cause) {
class ValidationError(message: String) : AppError(message)
class NetworkError(message: String, cause: Throwable? = null) : AppError(message, cause)
class DatabaseError(message: String, cause: Throwable? = null) : AppError(message, cause)
class BusinessLogicError(message: String) : AppError(message)
}
class UserRepository {
fun findUser(id: String): Result<User> {
return try {
// 模拟数据库查询
when (id) {
"1" -> Result.success(User("1", "Alice", "alice@example.com"))
"2" -> Result.success(User("2", "Bob", "bob@example.com"))
else -> Result.failure(AppError.DatabaseError("用户不存在:$id"))
}
} catch (e: Exception) {
Result.failure(AppError.DatabaseError("数据库查询失败", e))
}
}
}
class EmailService {
fun sendEmail(to: String, subject: String, body: String): Result<Unit> {
return try {
// 模拟邮件发送
if (!to.contains("@")) {
return Result.failure(AppError.ValidationError("无效的邮箱地址:$to"))
}
if (kotlin.random.Random.nextDouble() < 0.3) {
throw Exception("SMTP服务器连接失败")
}
println("邮件发送成功:$to")
Result.success(Unit)
} catch (e: Exception) {
Result.failure(AppError.NetworkError("邮件发送失败", e))
}
}
}
class UserService(
private val userRepository: UserRepository,
private val emailService: EmailService
) {
fun sendWelcomeEmail(userId: String): Result<Unit> {
return userRepository.findUser(userId)
.mapCatching { user ->
if (user.email.isBlank()) {
throw AppError.ValidationError("用户邮箱为空")
}
user
}
.flatMap { user ->
emailService.sendEmail(
to = user.email,
subject = "欢迎加入我们!",
body = "亲爱的 ${user.name},欢迎加入我们的平台!"
)
}
}
fun processUserBatch(userIds: List<String>): Map<String, Result<Unit>> {
return userIds.associateWith { userId ->
sendWelcomeEmail(userId)
}
}
}
data class User(val id: String, val name: String, val email: String)
fun main() {
val userRepository = UserRepository()
val emailService = EmailService()
val userService = UserService(userRepository, emailService)
println("=== 错误传播示例 ===")
// 单个用户处理
listOf("1", "2", "999").forEach { userId ->
println("\n处理用户:$userId")
userService.sendWelcomeEmail(userId).fold(
onSuccess = { println("欢迎邮件发送成功") },
onFailure = { error ->
when (error) {
is AppError.ValidationError -> println("验证错误:${error.message}")
is AppError.NetworkError -> println("网络错误:${error.message}")
is AppError.DatabaseError -> println("数据库错误:${error.message}")
is AppError.BusinessLogicError -> println("业务逻辑错误:${error.message}")
else -> println("未知错误:${error.message}")
}
}
)
}
println("\n=== 批量处理示例 ===")
// 批量处理
val batchResults = userService.processUserBatch(listOf("1", "2", "999", "invalid"))
val successCount = batchResults.values.count { it.isSuccess }
val failureCount = batchResults.values.count { it.isFailure }
println("批量处理结果:成功 $successCount,失败 $failureCount")
batchResults.forEach { (userId, result) ->
val status = if (result.isSuccess) "✓" else "✗"
val message = result.fold(
onSuccess = { "成功" },
onFailure = { it.message }
)
println("$status 用户 $userId: $message")
}
}下一步
掌握了异常处理后,让我们学习Kotlin中的文件处理操作。
下一章: 文件处理
练习题
- 创建一个网络请求库,使用Result类型处理各种网络错误
- 实现一个配置文件解析器,包含完整的错误处理和验证
- 设计一个批量数据处理系统,支持错误恢复和重试机制
- 编写一个日志系统,安全地处理文件写入异常
- 创建一个用户注册系统,包含多层验证和错误处理