创建自定义异常
可以通过创建继承内置 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
类。它有两个直接子类:Error
和 Exception
:
-
Error
子类:表示应用程序可能无法自行恢复的严重基础性问题,这些问题通常不需要尝试处理。例如:OutOfMemoryError
(内存不足错误)、StackOverflowError
(栈溢出错误)。 -
Exception
子类:用于表示可能需要在代码中处理的异常情况。例如:RuntimeException
(运行时异常)、IOException
(输入输出异常)。
异常与 Java
、Swift
和 Objective-C
的互操作性
由于 Kotlin
将所有异常视为未检查异常,当从区分检查异常和未检查异常的语言(如 Java
、Swift
和 Objective-C
)调用 Kotlin
代码时,可能会导致一些问题。为了解决 Kotlin
与这些语言在异常处理上的差异,可以使用 @Throws
注解。该注解用于提醒调用者可能抛出的异常。
@Throws
注解用于标记 Kotlin
函数可能抛出的异常,以便在 Java
、Swift
或 Objective-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());
}
}
}