2-2-43 快速掌握Kotlin-@Throws 注解

26 阅读5分钟

Kotlin 中的 @Throws 注解详解

1. 什么是 @Throws

@Throws 是一个 Kotlin 注解,用于显式声明函数可能抛出的受检异常(Checked Exceptions),以便与 Java 代码互操作。在纯 Kotlin 代码中通常不需要使用。

2. Kotlin 与 Java 异常处理的差异

Java 的异常体系

// Java 代码
public class JavaExample {
    // 受检异常:必须声明或处理
    public void readFile() throws IOException {
        // 可能抛出 IOException
    }
    
    // 非受检异常:不需要声明
    public void divide(int a, int b) {
        if (b == 0) {
            throw new RuntimeException("除零异常");
        }
    }
}

Kotlin 的异常处理

// Kotlin 代码 - 没有受检异常概念
fun readFile() {
    // 可能抛出 IOException,但不需要声明
    // 所有异常在 Kotlin 中都是"非受检的"
}

fun divide(a: Int, b: Int) {
    if (b == 0) {
        throw RuntimeException("除零异常")
    }
}

3. @Throws 的基本用法

基本语法

import java.io.IOException

@Throws(IOException::class)
fun readFile(path: String): String {
    // 可能抛出 IOException
    return java.io.File(path).readText()
}

// 声明多个异常
@Throws(IOException::class, IllegalArgumentException::class)
fun processFile(path: String) {
    if (path.isEmpty()) {
        throw IllegalArgumentException("路径不能为空")
    }
    // 文件处理逻辑
}

4. 主要使用场景

场景1:供 Java 代码调用的 Kotlin 函数

// Kotlin 代码
class FileProcessor {
    @Throws(IOException::class)
    fun processLargeFile(file: File): List<String> {
        // 可能抛出 IOException
        return file.readLines()
    }
}

// Java 代码中调用
public class JavaClient {
    void process() {
        FileProcessor processor = new FileProcessor();
        try {
            List<String> lines = processor.processLargeFile(new File("data.txt"));
        } catch (IOException e) {  // 可以捕获声明的异常
            e.printStackTrace();
        }
    }
}

场景2:实现 Java 接口

// Java 接口
interface JavaService {
    String fetchData() throws IOException;
}

// Kotlin 实现
class KotlinService : JavaService {
    @Throws(IOException::class)  // 必须添加 @Throws
    override fun fetchData(): String {
        // 可能抛出 IOException
        return fetchFromNetwork()
    }
    
    private fun fetchFromNetwork(): String {
        // 模拟网络请求
        throw IOException("网络错误")
    }
}

场景3:覆盖 Java 基类的方法

// Java 基类
public abstract class DataReader {
    public abstract String read() throws IOException;
}

// Kotlin 子类
class FileDataReader : DataReader() {
    @Throws(IOException::class)  // 必须添加 @Throws
    override fun read(): String {
        return File("data.txt").readText()
    }
}

5. @Throws 对字节码的影响

@Throws 注解

// Kotlin 代码
fun unsafeOperation() {
    throw IOException("错误")
}

// 编译后的字节码(简化)
// public final void unsafeOperation() {
//   throw new IOException("错误");
// }
// 没有 throws 声明

@Throws 注解

// Kotlin 代码
@Throws(IOException::class)
fun safeOperation() {
    throw IOException("错误")
}

// 编译后的字节码(简化)
// public final void safeOperation() throws IOException {
//   throw new IOException("错误");
// }
// 有 throws IOException 声明

6. 不同类型异常的示例

IOException(IO异常)

import java.io.IOException

class FileManager {
    @Throws(IOException::class)
    fun copyFile(source: String, dest: String) {
        val sourceFile = java.io.File(source)
        val destFile = java.io.File(dest)
        sourceFile.copyTo(destFile, overwrite = true)
    }
}

SQLException(数据库异常)

import java.sql.*

class DatabaseService {
    @Throws(SQLException::class)
    fun executeQuery(sql: String): ResultSet {
        val connection: Connection = // 获取连接
        val statement = connection.createStatement()
        return statement.executeQuery(sql)
    }
}

自定义异常

// 自定义异常类
class ValidationException(message: String) : Exception(message)

class UserService {
    @Throws(ValidationException::class)
    fun validateUser(user: User) {
        if (user.name.isBlank()) {
            throw ValidationException("用户名不能为空")
        }
        if (user.age < 0) {
            throw ValidationException("年龄无效")
        }
    }
}

7. 与 try-catch 的结合使用

Kotlin 中捕获异常

fun processData() {
    try {
        readFileWithThrows("data.txt")
    } catch (e: IOException) {
        // 处理 IOException
        println("文件读取错误: ${e.message}")
    } catch (e: Exception) {
        // 处理其他异常
        println("未知错误: ${e.message}")
    }
}

@Throws(IOException::class)
fun readFileWithThrows(path: String): String {
    // 可能抛出 IOException
    return File(path).readText()
}

try-with-resources 的 Kotlin 等价写法

// 自动关闭资源
fun readFileAutoClose(path: String): String {
    File(path).bufferedReader().use { reader ->
        // 自动关闭 reader
        return reader.readText()
    }
}

// 声明多个资源
fun copyFileAutoClose(source: String, dest: String) {
    File(source).inputStream().use { input ->
        File(dest).outputStream().use { output ->
            input.copyTo(output)
        }
    }
}

8. 实际应用示例

示例1:REST API 控制器

import org.springframework.web.bind.annotation.*
import java.io.IOException

@RestController
@RequestMapping("/api/files")
class FileController {
    
    @PostMapping("/upload")
    @Throws(IOException::class)
    fun uploadFile(@RequestParam("file") file: MultipartFile): ApiResponse {
        // 保存文件,可能抛出 IOException
        val savedFile = saveFile(file)
        return ApiResponse.success(savedFile)
    }
    
    private fun saveFile(file: MultipartFile): SavedFile {
        // 实际保存逻辑
        throw IOException("磁盘空间不足")
    }
}

示例2:Android 网络请求

import android.os.AsyncTask
import java.io.IOException

class DownloadTask : AsyncTask<String, Void, String>() {
    
    @Throws(IOException::class)
    override fun doInBackground(vararg params: String): String {
        val url = params[0]
        // 执行网络请求,可能抛出 IOException
        return downloadFromUrl(url)
    }
    
    private fun downloadFromUrl(url: String): String {
        // 实际下载逻辑
        throw IOException("网络连接失败")
    }
    
    override fun onPostExecute(result: String) {
        // 处理结果
    }
}

示例3:多线程异常处理

import java.util.concurrent.*

class ThreadPoolExample {
    private val executor = Executors.newFixedThreadPool(4)
    
    @Throws(ExecutionException::class, InterruptedException::class)
    fun executeTask(): String {
        val future: Future<String> = executor.submit {
            // 任务逻辑,可能抛出异常
            performTask()
        }
        
        // get() 可能抛出 ExecutionException 或 InterruptedException
        return future.get()
    }
    
    private fun performTask(): String {
        // 模拟任务
        if (Random().nextBoolean()) {
            throw IOException("任务执行失败")
        }
        return "成功"
    }
}

9. 最佳实践与注意事项

最佳实践1:只在需要时使用

// ❌ 不需要 @Throws 的情况
fun localCalculation(a: Int, b: Int): Int {
    return a + b  // 不会抛出异常
}

// ✅ 需要 @Throws 的情况
@Throws(IOException::class)
fun networkRequest(url: String): String {
    // 执行网络请求
}

最佳实践2:明确异常类型

// ❌ 过于宽泛
@Throws(Exception::class)
fun vagueMethod() { /* ... */ }

// ✅ 明确具体异常
@Throws(IOException::class, SQLException::class)
fun specificMethod() { /* ... */ }

最佳实践3:文档注释

/**
 * 读取文件内容
 * @param path 文件路径
 * @return 文件内容
 * @throws IOException 当文件不存在或读取失败时抛出
 */
@Throws(IOException::class)
fun readDocumentedFile(path: String): String {
    return File(path).readText()
}

10. 常见问题与解决方案

问题1:忘记添加 @Throws

// Kotlin 实现 Java 接口时忘记 @Throws
class ServiceImpl : JavaService {
    // ❌ 编译错误:必须添加 @Throws
    override fun fetchData(): String {
        throw IOException("错误")
    }
}

// ✅ 正确方式
class ServiceImpl : JavaService {
    @Throws(IOException::class)
    override fun fetchData(): String {
        throw IOException("错误")
    }
}

问题2:Kotlin 调用 Java 异常方法

// Java 方法声明异常
public class JavaUtils {
    public static void riskyMethod() throws IOException {
        // ...
    }
}

// Kotlin 中调用
fun callJavaMethod() {
    // Kotlin 不强制处理异常,但可以捕获
    try {
        JavaUtils.riskyMethod()
    } catch (e: IOException) {
        // 处理异常
    }
}

问题3:Lambda 表达式中的异常

// 在 lambda 中抛出受检异常需要特殊处理
fun processWithException(block: () -> Unit) {
    try {
        block()
    } catch (e: Exception) {
        // 处理异常
    }
}

// 使用
processWithException {
    // 不能在 lambda 中直接抛出受检异常
    // throw IOException("错误")  // 编译错误
    
    // 需要使用 runCatching 或 try-catch
    runCatching {
        throw IOException("错误")
    }.onFailure { /* 处理异常 */ }
}

11. 替代方案

使用 Result 类型(推荐)

sealed class Result<out T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Failure(val exception: Throwable) : Result<Nothing>()
}

fun readFileSafe(path: String): Result<String> {
    return try {
        val content = File(path).readText()
        Result.Success(content)
    } catch (e: IOException) {
        Result.Failure(e)
    } catch (e: Exception) {
        Result.Failure(e)
    }
}

// 使用
val result = readFileSafe("data.txt")
when (result) {
    is Result.Success -> println(result.value)
    is Result.Failure -> println("错误: ${result.exception.message}")
}

使用 Kotlin 的 runCatching

fun processFile(path: String) {
    val result = runCatching {
        File(path).readText()
    }
    
    result.onSuccess { content ->
        println("文件内容: $content")
    }.onFailure { exception ->
        println("读取失败: ${exception.message}")
    }
}

总结

@Throws 注解是 Kotlin 与 Java 互操作的重要桥梁,但应谨慎使用:

  1. 只在需要时使用:纯 Kotlin 项目通常不需要
  2. 用于 Java 互操作:当 Kotlin 代码需要被 Java 调用时
  3. 实现 Java 接口/继承类时必需
  4. 考虑替代方案:在纯 Kotlin 代码中考虑使用 Result 类型或 runCatching

记住:Kotlin 的设计哲学是避免受检异常,因此 @Throws 主要用于解决与 Java 的兼容性问题。