Kotlin 2.1.0 入门教程(十三)异常、Nothing

创建自定义异常

可以通过创建继承内置 Exception 类来定义自定义异常。这允许你创建更符合应用程序需求的特定错误类型。

要创建一个自定义异常,可以定义一个继承 Exception 的类:

kotlin 复制代码
class MyException : Exception("My message")

在这个例子中,有一个默认的错误信息 My message,如果你愿意,也可以留空。

Kotlin 中的异常是有状态的对象,携带了与创建它们的上下文相关的信息,称为堆栈跟踪。避免使用对象声明来创建异常。相反,每次需要异常时,创建一个新的异常实例。这样可以确保异常的状态准确反映特定的上下文。

自定义异常也可以是任何已存在的异常子类的子类,例如 ArithmeticException 子类:

kotlin 复制代码
class NumberTooLargeException : ArithmeticException("My message")

如果你想创建自定义异常的子类,必须将父类声明为 open,因为默认情况下类是 final 的,否则无法被子类化。

kotlin 复制代码
open class MyCustomException(message: String) : Exception(message)

class SpecificCustomException : MyCustomException("Specific error message")

自定义异常的行为与内置异常完全相同。你可以使用 throw 关键字抛出它们,并使用 try-catch-finally 块来处理它们。

在具有多种错误场景的应用程序中,创建异常层次结构可以使代码更清晰和更具针对性。你可以通过使用抽象类或密封类作为通用异常特性的基类,并为详细的异常类型创建特定子类来实现这一点。此外,带有可选参数的自定义异常提供了灵活性,允许使用不同的消息进行初始化,从而实现更细粒度的错误处理。

让我们看一个使用密封类 AccountException 作为异常层次结构基类的示例,以及子类 APIKeyExpiredException,它展示了如何使用可选参数来增强异常的详细信息:

kotlin 复制代码
// 创建一个抽象类作为与账户相关错误的异常层次结构的基类。
sealed class AccountException(message: String, cause: Throwable? = null) :
Exception(message, cause)

// 创建一个 AccountException 的子类。
class InvalidAccountCredentialsException : AccountException("Invalid account credentials detected")

// 创建一个 AccountException 的子类,允许添加自定义消息和原因。
class APIKeyExpiredException(message: String = "API key expired", cause: Throwable? = null) : AccountException(message, cause)

Nothing 类型

每个表达式都有一个类型。表达式 throw IllegalArgumentException() 的类型是 Nothing,这是一个内置类型,是所有其他类型的子类型,也被称为底部类型。这意味着 Nothing 可以用作返回类型或泛型类型,在期望任何其他类型的地方使用,而不会导致类型错误。

Nothing 是一种特殊类型,用于表示永远不会成功完成的函数或表达式,可能是因为它们总是抛出异常,或者进入了无限循环等无法结束的执行路径。你可以使用 Nothing 来标记尚未实现的函数或设计为总是抛出异常的函数,从而向编译器和代码阅读者清晰地表明你的意图。如果编译器在函数签名中推断出 Nothing 类型,它会发出警告。显式地将 Nothing 定义为返回类型可以消除此警告。

以下代码展示了 Nothing 类型的使用,其中编译器会将函数调用后的代码标记为不可达:

kotlin 复制代码
class Person(val name: String?)

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

fun main() {
    val person = Person(name = null)
    val s: String = person.name ?: fail("Name required")
}

TODO() 函数同样使用了 Nothing 类型,它作为一个占位符,用于标记代码中需要未来实现的部分。

kotlin 复制代码
fun notImplementedFunction(): Int {
    TODO("This function is not yet implemented")
}

fun main() {
    // 抛出 NotImplementedError 异常。
    val result = notImplementedFunction()
    println(result)
}

TODO() 函数总是抛出 NotImplementedError 异常。

异常类

让我们探讨一些常见的异常类型,它们都是 RuntimeException 类的子类:

ArithmeticException:当无法执行算术操作时(如除以零),会抛出此异常。

kotlin 复制代码
// 抛出 ArithmeticException 异常。
val example = 2 / 0

IndexOutOfBoundsException:当访问的索引超出范围时(如数组或字符串的索引),会抛出此异常。

kotlin 复制代码
val myList = mutableListOf(1, 2, 3)

// 抛出 IndexOutOfBoundsException 异常。
myList.removeAt(3)

为了避免此异常,可以使用更安全的替代方法,如 getOrNull() 函数:

kotlin 复制代码
val myList = listOf(1, 2, 3)

// 返回 null,而不是抛出 IndexOutOfBoundsException 异常。
val element = myList.getOrNull(3)

println("Element at index 3: $element")

NoSuchElementException:当访问集合中不存在的元素时(如使用 first()last()elementAt() 方法),会抛出此异常。

kotlin 复制代码
val emptyList = listOf<Int>()

// 抛出 NoSuchElementException 异常。
val firstElement = emptyList.first()

为了避免此异常,可以使用更安全的替代方法,如 firstOrNull() 函数:

kotlin 复制代码
val emptyList = listOf<Int>()

// 返回 null,而不是抛出 NoSuchElementException 异常。
val firstElement = emptyList.firstOrNull()

println("First element in empty list: $firstElement")

NumberFormatException:当尝试将字符串转换为数字类型,但字符串格式不正确时,会抛出此异常。

kotlin 复制代码
val string = "This is not a number"

// 抛出 NumberFormatException 异常。
val number = string.toInt()

为了避免此异常,可以使用更安全的替代方法,如 toIntOrNull() 函数:

kotlin 复制代码
val nonNumericString = "not a number"

// 返回 null,而不是抛出 NumberFormatException 异常。
val number = nonNumericString.toIntOrNull()

println("Converted number: $number")

NullPointerException:当应用程序尝试使用值为 null 的对象引用时,会抛出此异常。尽管空安全特性显著减少了 NullPointerException 的风险,但在使用 !! 操作符或与缺乏空安全特性的 Java 代码交互时,仍然可能发生此异常。

kotlin 复制代码
val text: String? = null

// 抛出 NullPointerException 异常。
println(text!!.length)

Kotlin 中,所有异常都是未检查的(unchecked),你不需要显式捕获它们。但你仍然可以根据需要灵活地捕获这些异常。

异常层次结构

异常层次结构的根是 Throwable 类。它有两个直接子类:ErrorException

  • Error 子类:表示应用程序可能无法自行恢复的严重基础性问题,这些问题通常不需要尝试处理。例如:OutOfMemoryError(内存不足错误)、StackOverflowError(栈溢出错误)。

  • Exception 子类:用于表示可能需要在代码中处理的异常情况。例如:RuntimeException(运行时异常)、IOException(输入输出异常)。

异常与 JavaSwiftObjective-C 的互操作性

由于 Kotlin 将所有异常视为未检查异常,当从区分检查异常和未检查异常的语言(如 JavaSwiftObjective-C)调用 Kotlin 代码时,可能会导致一些问题。为了解决 Kotlin 与这些语言在异常处理上的差异,可以使用 @Throws 注解。该注解用于提醒调用者可能抛出的异常。

@Throws 注解用于标记 Kotlin 函数可能抛出的异常,以便在 JavaSwiftObjective-C 中调用时能够正确处理。

kotlin 复制代码
import kotlin.jvm.Throws

@Throws(IOException::class)
fun readFile(filePath: String): String {
    val file = File(filePath)
    if (!file.exists()) {
        throw IOException("File not found: $filePath")
    }
    return file.readText()
}

Java 中调用此函数时,编译器会强制处理 IOException

java 复制代码
public class Main {
    public static void main(String[] args) {
        try {
            String content = KotlinFileReader.readFile("nonexistent.txt");
            System.out.println(content);
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }
}
相关推荐
REDcker16 小时前
Android WebView 版本升级方案详解
android·音视频·实时音视频·webview·js·编解码
麦兜*16 小时前
【springboot】图文详解Spring Boot自动配置原理:为什么@SpringBootApplication是核心?
android·java·spring boot·spring·spring cloud·tomcat
le16161617 小时前
Android 依赖种类及区别:远程仓库依赖、打包依赖、模块依赖、本地仓库依赖
android
lxysbly17 小时前
psp模拟器安卓版带金手指
android
云上凯歌17 小时前
02 Spring Boot企业级配置详解
android·spring boot·后端
hqiangtai18 小时前
Android 高级专家技术能力图谱
android·职场和发展
aqi0018 小时前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
stevenzqzq18 小时前
Android Koin 注入入门教程
android·kotlin
zFox18 小时前
三、Kotlin协程+异步加载+Loading状态
kotlin·android jetpack·协程
炼金术18 小时前
SkyPlayer v1.1.0 - 在线视频播放功能更新
android·ffmpeg