如何为具有协变返回类型的方法的类型层次结构编写通用扩展方法。
例如以下类型:
sealed interface MyInterface {
fun map(): MyInterface
class ImplA : MyInterface {
override fun map(): ImplA = ImplA()
}
class ImplB : MyInterface {
override fun map(): ImplB = ImplB()
}
}
map()
调用其中一种子类型会返回具体子类型。所以我希望能够编写这样的扩展方法来利用它:
fun <T : MyInterface> T.doThingWithMap(): T {
return map()
}
val a: MyInterface.ImplA = MyInterface.ImplA().doThingWithMap()
val b: MyInterface.ImplB = MyInterface.ImplB().doThingWithMap()
但这不能与
Type mismatch: inferred type is MyInterface but T was expected
一起编译。我怎样才能获得这种类型安全的协变行为?
您的代码是类型安全的,只是因为您引入了一种非正式约定,其中
MyInterface
的子类型在其 map
函数中返回相同的类型。编译器不理解这个约定。此外,当我们编写泛型函数时,编译器不会遍历 MyInterface
的所有已知可能子类型来检查代码对于所有这些特定子类型是否安全。它唯一看到的是接收者是“MyInterface
的某种子类型”,因此从编译器的角度来看,map()
仅返回MyInterface
。
如果您可以控制
MyInterface
并且您知道始终满足约定,则可以简单地转换返回值:
fun <T : MyInterface> T.doThingWithMap(): T {
@Suppress("UNCHECKED_CAST")
return map() as T
}
如果你经常像这样使用
map()
,你可以创建另一个扩展函数,例如mapTyped
并在其他地方重复使用。
更好的解决方案是,如果我们可以指定
map
返回与接收者相同的类型。有些语言支持这种功能,这通常称为“自我类型”,它可能看起来像这样:
sealed interface MyInterface {
fun map(): SELF // doesn't compile
}
这将立即解决问题。不幸的是,Kotlin 从 1.9.23 版本开始不支持此功能。
提供“self”类型的一种方法是使类型对自身通用。这非常冗长,几乎是一种解决方法,但它按预期工作:
sealed interface MyInterface<T : MyInterface<T>> {
fun map(): T
class ImplA : MyInterface<ImplA> {
override fun map(): ImplA = ImplA()
}
class ImplB : MyInterface<ImplB> {
override fun map(): ImplB = ImplB()
}
}
fun <T : MyInterface<T>> T.doThingWithMap(): T {
return map()
}
但是,您应该考虑是否值得付出努力。未经检查的铸造是一个非常简单的解决方案,如果您的外壳是您控制的密封类型,那么这是非常安全的。