我不明白。为什么Scala支持覆盖方法作为字段:
abstract class A {
def i: Int;
}
class B extends A {
val i = 123;
}
val obj:A = new B();
println(obj.i)
方法i
是字段i
。为什么?
我不明白。为什么Scala支持覆盖方法作为字段:
abstract class A {
def i: Int;
}
class B extends A {
val i = 123;
}
val obj:A = new B();
println(obj.i)
方法i
是字段i
。为什么?
查看这里发生了什么的一种快速方法是编译此代码并用于javap
检查生成的类。在这里,我们得到以下内容A
:
public abstract class A implements scala.ScalaObject {
public abstract int i();
public A();
}
对于B
(使用-p
,所以我们可以看到任何私有成员):
public class B extends A implements scala.ScalaObject {
private final int i;
public int i();
public B();
}
所以最后val
只是作为一个带有 getter 的私有字段——很像你在惯用的 Java 中编写的内容,尽管命名约定不同(并且不是真正的约定,因为编译器会为你处理它)。
Travis 的回答解释了如何在实现级别上,一些方法可以被 Scala 中的字段覆盖,但不解释为什么。为什么问题的答案与两个编程语言设计原则有关:统一访问原则和里氏替换原则。
统一访问原则本质上说调用者不必区分通过方法读取属性和通过字段读取属性——这两个调用应该看起来相同,并且不应该背叛它们是否涉及存储或计算。
Liskov 替换原则本质上说子类型必须遵守其父类型的所有合同。允许子类型遵守比其父类型更强的合同,但不得违反其父类型的任何合同。
在 Scala 中,声明def
不带参数列表的 a 意味着某种约定。也就是说,这意味着访问关联的属性将返回声明类型的值。由于不能保证引用透明性,后续访问可能会或可能不会返回相同的值。
显然,aval
满足相同的合同。但是,aval
做出了更强有力的保证:每次访问都会产生相同的值。由于这是一个更强大val
的契约,因此子类型使用 a来实现 a完全符合 Liskov 替换原则def
。同样,禁止子类型使用 adef
来实现 a val
。