我有一个关于访问修饰符覆盖的问题。
首先我们看一下下面的代码。
fun main() {
val b: AAA = BBB()
b.testA()
}
open class AAA() {
fun testA() {
println("This is testA()")
}
}
open class BBB() : AAA()
结果:
This is testA()
这段代码没问题,
b
已经打印了testA()
类的
AAA.
我们看下面的代码。
fun main() {
val b: AAA = BBB()
b.testA()
}
open class AAA() {
open fun testA() {
println("This is testA()")
}
}
class BBB() : AAA() {
override fun testA() {
println("This is overrided testA() ")
}
}
结果:
This is overridden testA()
这段代码也没有问题。
因为
testA()
已被覆盖,所以调用了 testA()
类的 BBB
。
现在,这是我要问的代码。
fun main() {
val b: AAA = BBB()
b.testA()
}
open class AAA() {
protected open fun testA() {
println("This is testA()")
}
}
class BBB() : AAA() {
public override fun testA() {
println("This is overridden testA()")
}
}
这段代码不会被执行。 IDE 抱怨
Cannot access 'testA': it is protected in 'AAA'
。
虽然
testA()
的AAA
是protected
,但它在public
中被重写为BBB
,所以我认为自然会像第二个代码一样调用testA()
的重写BBB
。
有什么问题吗? 我错过了什么吗? 我误解了继承和访问修饰符的哪一部分?
虽然 AAA 的 testA() 是受保护的,但它在 BBB 中被重写为 public,所以我认为自然会像第二个代码一样调用 BBB 的重写 testA()。
误解是关于何时事情发生。
在运行时,方法调用将动态分派到
b
指向的实例的实际类型。这就是为什么当你实际运行代码时,会使用 BBB
中方法的 implementation(因为
b
实际上指向 BBB
的实例)。这就是覆盖的工作原理。
然而,在编译时,重要的是用于调用方法
b
的引用变量 testA()
的类型。由于 b
的类型被显式标记为 AAA
,编译器会根据该类型分析您的代码,并且该方法将被解析为 AAA.testA()
以便进行编译。可见性也将基于此进行检查。如果您尝试导航到 IDE 中的函数定义,您实际上可以看到这一点(它会引导您到 AAA
中的定义,而不是 BBB
中的定义)。
简单来说,您可以想象编译器将
b
视为 AAA
,而不对代码运行时它将指向的实际值进行任何假设。
如果您希望编译最后一个代码片段,则必须声明
b
类型为 BBB
(或删除显式类型并让编译器自动将类型推断为 BBB
)。
fun main() {
val b: BBB = BBB() // or simply val b = BBB()
b.testA()
}
明确地说:
b
是AAA
类型,因此不允许访问b.testA()
,因为它受到保护。
无论
b
对象实际上是什么,您只能使用 AAA
定义的成员。毕竟, b
也可能是其他一些 AAA
实例,但 not 具有可访问的 testA()
功能。
如果你知道
b
实际上不仅是AAA
而且还是BBB
,你可以像这样投射它:
val reallyB: BBB = b as BBB
虽然它在底层是同一个对象,但 varialbe
reallyB
现在是 BBB
类型,因此您可以访问它的任何成员。 testA
现已公开,因此有效:
reallyB.testA()