Skip to content

异常处理

概述

异常处理是程序健壮性的重要保障。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中的文件处理操作。

下一章: 文件处理

练习题

  1. 创建一个网络请求库,使用Result类型处理各种网络错误
  2. 实现一个配置文件解析器,包含完整的错误处理和验证
  3. 设计一个批量数据处理系统,支持错误恢复和重试机制
  4. 编写一个日志系统,安全地处理文件写入异常
  5. 创建一个用户注册系统,包含多层验证和错误处理

本站内容仅供学习和研究使用。