Why it's impossible to override `var` with `de

2019-01-15 19:45发布

While I understand why a var cannot override a val in subclass and vice versa, I am unable to understand why does Scala not allow a def in subclass to override a var in superclass

class Car {
  var age = 32
}

class SedanCar extends Car {
  override def age = 54
}

As var is mutable why not allow a def to override it? Can anyone please help me in understanding this?

2条回答
在下西门庆
2楼-- · 2019-01-15 20:04

I think you have a problem with the concept. From Scala references:

A variable declaration var x: T is equivalent to declarations of a getter function x and a setter function x_=, defined as follows:

def x: T
def x_= (y: T ): Unit

So you are trying to override a getter by "age = 54". But now you do not have any use for the setter.

Hope you get what I mean. I think why people "minus" your question is because you are not thinking in the mindset of Scala here.

查看更多
放荡不羁爱自由
3楼-- · 2019-01-15 20:05

That's related to the Liskov Substitution Principle: you can't assign weaker access privileges in subclass (even for Java). Making var a def makes the setter def x_= (y: T ): Unit private (as @Staix said). So in case when Seadan Car has formal type Car - it should not be accessed, but compiler can't find such cases in general (only formal types are known in compile-time), so such behavior is disabled like any weaker privilege:

 val car2 = new SeadanCar

 car.age = 422 //compiler error, can't mutate "def age"

 val car: Car = new SeadanCar

 car.age = 42 //now you had mutated immutable

The point of Substitutability Principle is behavior should not be changed after casting to the supertype.

On the other hand, scala could override only getter part of variable as @Régis Jean-Gilles said, but this is not so obvious solution, because intuitively user expects a var becoming immutable after override def. And it's actually violating Uniform Access Principle as you have to see your var as two services (reader and writer) instead of one it really is, while UAP-compatibilty requires the opposite: both reader and writer should be represented by one uniform notation.

P.S. Actually your question points to scala's UAP-compatibility incompleteness. As i said, overriding only var's reader and leaving writer as is will be inconsistent with UAP - it's blocking ability to override var itself (so you can't override var in both ways: computational and storage-like), even regardless that you already can't override it due to LSP. But current scala's solution is also problematic. You may find that you can:

 trait Car { def age: Int = 7; def age_=(a: Int) = {}}

 class SeadanCar extends Car { override def age: Int = 5}

But can't

 // just repeating your example

 trait Car { var age: Int = 7 } 

 class SeadanCar extends Car { override def age: Int = 5}

So scala's inheritance seems not to be compatible with UAP. IMHO, the big problem is that reader and var itself have identical names - so you can't distinguish them (when defining, not accessing). I'd solve it with something like:

 trait Car { def age_: Int = 7; def age_=(a: Int) = {}}
 class SeadanCarReadOnly extends Car { override def age: Int = 5} //can't compile as reader is closed
 class SeadanCarVar extends Car { override var age: Int = 5} 
 class SeadanCarReadOnly extends Car { override def age_: Int = 5}

 trait Car2 { var age = 100500 }
 class SeadanCarReadOnly extends Car2 { override def age_: Int = 5}

Note, that overriding age_ in my proposed example should lead to:

scalaxx> (new SeadanCarReadOnly).age //call age_ here
resxx: Int = 5

scalaxx> (new SeadanCarReadOnly).age_
resxx: Int = 5

Not like:

 trait Car2 { @BeanProperty var age = 100500 }
 class SeadanCarReadOnly extends Car2 { override def getAge: Int = 5}

 //which leads to inconsistency:

 scala> (new SedanCar()).age
 res6: Int = 30

 scala> (new SedanCar()).getAge
 res7: Int = 54

Of cource, such approach should disable overriding var age and def age_; def age_= simultaneously:

 trait Car2 { var age = 100500 }
 class SeadanCarReadOnly extends Car2 { 
    override var age = 17; 
    override def age_: Int = 5 //should be compile error here
 }

but this is hard to quickly implement it in Scala language due to backward compatibility

P.S./2 Just to mention, regarding mutability/immutabilty part of the question, you definetely can't do this (due to LSP):

 trait Car { var age: Int = 32 } //or without initial value
 class SedanCar extends Car { override val age = 42 }

And, due to LSP + UAP, shouldn't be able to do this:

 trait Car { def age: Int = 7; def age_=(a: Int) = {}}
 class SedanCar extends Car { override val age = 42 }

regardless the fact that you can :)

查看更多
登录 后发表回答