也许我累了,或者没有想清楚,或者这只是我以前从未尝试过的事情。
我有一个基本数据类型,我们称其为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。
让我们看看这两种情况:
- 函数
getFoo()
返回Foo。在子类型中,返回Bar。因为每个Bar都是一个Foo(因为Bar是Foo的子类型),你可以在每个使用基版本的地方使用getFoo()的重写版本——它总是返回一个Foo(恰好是Bar, Foo的特定类型)。一切都很好。 - 函数
setFoo()
接收一个Foo。在子类型中,您希望它接受Bar。现在,有人正在通过基类型的引用使用您的子类型。他们传递给setFoo()
一个Foo,它不是一个Bar。但是您的setFoo()
在这种情况下只接受Bar。这意味着您不能使用子类来代替基类,这违反了Liskov替换原则,并导致子类化的整个思想崩溃。因此,语言的选择是不允许子类接受Bar来代替Foo。
您还可以阅读协方差和逆变性,以进一步探索子类型何时可以代替其超类型。