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());
        }
    }
}
相关推荐
雾里看山31 分钟前
【MySQL】 表的约束(上)
android·mysql·adb
q567315232 小时前
无法在Django 1.6中导入自定义应用
android·开发语言·数据库·django·sqlite
a3158238063 小时前
Android设置个性化按钮按键的快捷启动应用
android·开发语言·framework·源码·android13
Henry_He3 小时前
SystemUI通知在阿拉伯语下布局方向RTL下appName显示异常
android
XJSFDX_Ali4 小时前
安卓开发,底部导航栏
android·java·开发语言
云罗张晓_za8986685 小时前
抖音“碰一碰”发视频:短视频社交的新玩法
android·c语言·网络·线性代数·矩阵·php
货拉拉技术8 小时前
记一次无障碍测试引发app崩溃问题的排查与解决
android·前端·程序员
GrimRaider9 小时前
【逆向工程】破解unity的安卓apk包
android·unity·游戏引擎·软件逆向
yzpyzp9 小时前
Jetpack之ViewBinding和DataBinding的区别
android
小墙程序员9 小时前
一文了解Android的build目录结构
android·gradle