1. 概述

在本篇文章中,我们将探讨 Kotlin 中为何没有“受检异常(checked exceptions)”,以及这种设计带来的影响。在此基础上,我们会深入讲解 Kotlin 提供的 @Throws 注解,以及它在 Java 互操作性中的关键作用。我们还会讨论在哪些场景下应该使用 @Throws,哪些场景下则应该避免使用。

2. Kotlin 中没有受检异常

和 Java 不同,Kotlin 不支持受检异常机制。这意味着在 Kotlin 中,我们不需要像 Java 那样在方法签名中使用 throws 声明异常。事实上,Kotlin 甚至根本就没有 throws 关键字。

来看一个例子:

fun throwJavaUnchecked() {
    throw IllegalArgumentException()
}

fun throwJavaChecked() {
    throw IOException()
}

上面的两个函数分别抛出了 Java 中的非受检异常(IllegalArgumentException)和受检异常(IOException),但在 Kotlin 中它们的处理方式是一样的 —— 都不需要在方法签名中声明异常

✅ 结论:在 Kotlin 中,无论抛出的是受检还是非受检异常,都不需要显式声明。

3. Java 互操作性问题

Kotlin 与 Java 的互操作性非常好,这是其一大优势。但正因为 Kotlin 不支持受检异常,当我们在 Java 中调用 Kotlin 函数时,就会遇到一些问题。

例如,我们从 Java 调用 throwJavaUnchecked() 并捕获 IllegalArgumentException 是没问题的:

public class Caller {

    public static void main(String[] args) {
        unchecked();
    }

    public static void unchecked() {
        try {
            ThrowsKt.throwJavaUnchecked();
        } catch (IllegalArgumentException e) {
            System.out.println("Caught something!");
        }
    }
}

因为 IllegalArgumentException 是非受检异常,Java 编译器不会报错。

❌ 但如果我们尝试从 Java 调用 throwJavaChecked() 并捕获 IOException

public static void checked() {
    try {
        ThrowsKt.throwJavaChecked();
    } catch (IOException e) {
        System.out.println("Won't even compile");
    }
}

编译器会报错:

java: exception java.io.IOException is never thrown in body of corresponding try statement

⚠️ 原因:Java 要求受检异常必须在方法签名中使用 throws 声明,否则不能捕获。

4. 使用 @Throws 注解

为了解决上述 Java 互操作性问题,Kotlin 提供了 @Throws 注解。

当我们使用 @Throws 注解一个 Kotlin 函数时,编译器会在生成的字节码中为该函数添加 throws 子句,这样 Java 调用方就可以正确地捕获受检异常。

示例:

@Throws(IOException::class)
fun throwJavaChecked() {
    throw IOException()
}

✅ Java 调用方现在可以这样使用:

try {
    ThrowsKt.throwJavaChecked();
} catch (IOException e) {
    System.out.println("It works this time!");
}

或者选择继续抛出:

public static void checked() throws IOException {
    ThrowsKt.throwJavaChecked();
}

⚠️ 注意:@Throws 只对 Java 调用者有意义,对 Kotlin 代码本身没有任何影响。

4.1. 注解的字节码表示

使用 @Throws 后,我们可以通过 javap 查看字节码确认其效果。

编译 Kotlin 文件:

kotlinc throws.kt

查看字节码:

javap -c -p com.baeldung.throwsannotation.ThrowsKt

输出如下:

public static final void throwJavaChecked() throws java.io.IOException;

可以看到,函数签名中确实包含了 throws java.io.IOException

而没有 @Throws 的函数则没有:

public static final void throwJavaUnchecked();

5. 总结

  • ✅ Kotlin 不支持受检异常机制。
  • @Throws 是 Kotlin 提供给 Java 调用者使用的注解,用于声明函数可能抛出的受检异常。
  • ❌ 在纯 Kotlin 项目中使用 @Throws 是没有意义的。
  • ✅ 使用 @Throws 后,Kotlin 编译器会在字节码中生成 throws 子句,从而实现与 Java 的无缝互操作。

📌 最佳实践建议:

  • 如果你的 Kotlin 函数会被 Java 代码调用,并且可能抛出受检异常,请务必加上 @Throws
  • 如果是纯 Kotlin 项目,无需使用该注解,保持代码简洁。

如需查看完整示例代码,欢迎访问:GitHub 项目地址


原始标题:A Guide to @Throws in Kotlin