编程Kotlin开发者

什么是Kotlin中的sealed接口(sealed interfaces),它们在实践中如何工作,与sealed类有什么不同?

用 Hintsage AI 助手通过面试

答案

问题的历史

在Kotlin的早期版本中,为了限制类型层次结构,使用了sealed类,允许开发人员通过限制在同一个文件中声明子类来显式控制允许的子类型。然而,对于某些任务来说,这不足够,特别是在需要通过接口描述类似层次结构时。为了解决这个问题,Kotlin 1.5开始引入了sealed修饰符用于接口。

问题

普通接口允许在项目的任何地方实现它们,这常常妨碍保证类型层次结构的绝对封闭性以及使用穷尽性类型检查(exhaustive when)。这可能导致运行时错误,无法静态检查所有变体。

解决方案

Sealed接口允许在一个文件内限制实现类的列表,提高了类型系统的可预测性和模式匹配的安全性。

sealed interface NetworkResult class Success(val data: String): NetworkResult class Failure(val error: Throwable): NetworkResult fun handle(result: NetworkResult) = when(result) { is Success -> println("Data: ${result.data}") is Failure -> println("Error: ${result.error}") // 所有变体都已考虑,不需要'else' }

关键特性:

  • Sealed接口允许单一层次结构:只能从同一个文件中继承类/对象。
  • Sealed接口可以由类和对象实现。
  • 扩展了when表达式的类型安全性,而无需显式考虑else。

具有陷阱的问题

可以将sealed接口添加到单独的文件中或在原始文件之外实现吗?

不可以。所有直接实现sealed接口的类型必须在同一文件中声明,否则编译器会报错。

sealed接口可以在声明文件外拥有子接口吗?

不可以。子接口也必须在同一文件中。整个层次结构在文件内部封闭。

sealed接口会影响运行时性能吗?

不会直接影响,但它们允许在编译阶段使用安全的类型检查,这减少了运行时错误的可能性,简化了代码,并可能间接加快测试速度。

常见错误和反模式

  • 在声明接口的文件之外声明sealed接口的实现类。
  • 添加过多的变体,导致维护困难。
  • 使用sealed接口,当变体层次结构可能变化时(不需要变体的封闭性)。

生活中的例子

负面案例

开发人员在一个文件中声明sealed接口NetworkAction,但实现分散在不同的类和文件中。问题在后期被发现:编译器发出违反规则的警告,尤其是在大型项目中,修复结构变得困难。

优点:

  • 允许在不编辑一个文件的情况下声明新的实现。

缺点:

  • 破坏了类型安全性保证。
  • 迫使代码重构,这在时间上是昂贵的。

正面案例

sealed接口OrderResult和类Success/Failure在同一个文件内部声明。when的检查始终是穷尽的,没有遗漏任何分支。

优点:

  • 安全性和处理的完整性。
  • 改善架构质量。

缺点:

  • 扩展性较差(所有更改只能通过此文件)。