Kotlin接收参数不能重写类型,但返回参数可以?



也许我累了,或者没有想清楚,或者这只是我以前从未尝试过的事情。

我有一个基本数据类型,我们称其为Foo,还有一个接口,我们称其为Problem,它公开了一些与Foo数据类型一起工作的函数。

其中一个接口函数返回一个Foo,其中一个函数接收一个Foo

然后,我从Foo继承,创建一个新的数据类型,让我们称之为Bar,然后我实现Problem接口,并在覆盖中,我覆盖Foo的类型为Bar

对于返回Foo的函数,可以使用Bar重写。但是,对于接收Foo的函数,使用Bar重写不起作用:

open class Foo(
open val name: String
)
interface Problem {
fun setFoo(foo: Foo)
fun getFoo() : Foo
}
data class Bar(
override val name: String
) : Foo(
name = name
)
class ProblemImpl : Problem {
override fun setFoo(foo: Bar) {
TODO("Not yet implemented")
}
override fun getFoo(): Bar {
TODO("Not yet implemented")
}
}

setFoo overrides nothing

现在,这似乎是显而易见的;只是不要重写这样的类型!您可以只传递Bar实例,并且由于多态性,它们将作为Foo实例接收。但是,由于复杂的原因,我不会在这里深入讨论,我不能这样做(想想在库中声明Dao接口,并在使用Room注释的应用程序中重写它…)

我想我可以让接口被类型参数化…如:

interface Problem<T: Foo> {
fun setFoo(foo: T)
fun getFoo() : T
}
事实上,我想我已经回答了我自己的问题……我现在尝试使用模板,但我有一种预感,它会在编译时失败,因为Room注释处理器可能无法处理模板接口…在任何情况下,有没有人能告诉我为什么接收的函数不能用子类型重写,而返回的函数可以?

在任何情况下,谁能告诉我为什么接收的函数不能用子类型重写,而返回的函数可以?

这个限制是由Liskov替换原则引起的:如果Bar是Foo的子类型,那么在使用Foo的任何地方都应该能够使用Bar。

让我们看看这两种情况:

  1. 函数getFoo()返回Foo。在子类型中,返回Bar。因为每个Bar都是一个Foo(因为Bar是Foo的子类型),你可以在每个使用基版本的地方使用getFoo()的重写版本——它总是返回一个Foo(恰好是Bar, Foo的特定类型)。一切都很好。
  2. 函数setFoo()接收一个Foo。在子类型中,您希望它接受Bar。现在,有人正在通过基类型的引用使用您的子类型。他们传递给setFoo()一个Foo,它不是一个Bar。但是您的setFoo()在这种情况下只接受Bar。这意味着您不能使用子类来代替基类,这违反了Liskov替换原则,并导致子类化的整个思想崩溃。因此,语言的选择是不允许子类接受Bar来代替Foo。

您还可以阅读协方差和逆变性,以进一步探索子类型何时可以代替其超类型。

最新更新