scala> class C
defined class C
scala> class subC extends C
defined class subC
scala> class A { type T = C}
defined class A
scala> class subA extends A { override type T = subC}
<console>:10: error: overriding type T in class A, which equals C;
type T has incompatible type
class subA extends A { override type T = subC}
^
In the example above, I get an error message, that I can not override the type field in class A
( even if the chosen type subC
extends the class C
).
Is overriding a type field possible at all ? And if yes, what is wrong with the example above ?
Fields can't be overridden; they're not accessed polymorphically in the first place - you're just declaring a new field in each case. It compiles because in each case the compile-time type of the expression is enough to determine which field called number you mean.
Java Inheritance BasicsFields cannot be overridden, but can be "shadowed" in subclasses.
In any object-oriented programming language, Overriding is a feature that allows a subclass to provide a specific implementation of a method or field that is already provided by one of its super-classes.
You cannot override a non-virtual or static method. The overridden base method must be virtual , abstract , or override . An override declaration cannot change the accessibility of the virtual method. Both the override method and the virtual method must have the same access level modifier.
You wouldn't speak of 'overriding' with respect to types, but rather narrowing their bounds.
type T
... no boundstype T <: C
... T
is C
or a subtype of C
(which is called upper bound)type T >: C
... T
is C
or a supertype of C
(which is called lower bound)type T = C
... T
is exactly C
(type alias)Therefore, if T
is a type member of trait A
, and SubA
is a subtype of A
, in case (2) SubA
may narrow T
to a more particular subtype C
, whereas in case (3) it could narrow it to a higher supertype of C
. Case (1) doesn't impose any restrictions for SubA
, while case (4) means that T
is 'final' so to speak.
This has consequences for the useability of T
in A
—whether it may appear as a method argument's type or a method's return type.
Example:
trait C { def foo = () }
trait SubC extends C { def bar = () }
trait MayNarrow1 {
type T <: C // allows contravariant positions in MayNarrow1
def m(t: T): Unit = t.foo // ...like this
}
object Narrowed1 extends MayNarrow1 {
type T = SubC
}
object Narrowed2 extends MayNarrow1 {
type T = SubC
override def m(t: T): Unit = t.bar
}
It is possible to define method m
in MayNarrow1
because type T
occurs in contravariant position (as a method argument's type), therefore it is still valid even if T
is narrowed down in a subtype of MayNarrow1
(the method body can treat t
as if it were type C
).
In contrast, type T = C
inevitably fixes T
, which would kind of correspond to making a method final
. By fixing T
, it can be used in a covariant position (as a method's return type):
trait Fixed extends MayNarrow1 {
type T = C // make that T <: C to see that it won't compile
final def test: T = new C {}
}
You can now easily see that it must be forbidden to further 'override' T
:
trait Impossible extends Fixed {
override type T = SubC
test.bar // oops...
}
To be complete, here is the less common case of a lower bound:
trait MayNarrow2 {
type T >: SubC // allows covariant positions in MayNarrow2
def test: T = new SubC {}
}
object Narrowed3 extends MayNarrow2 {
type T = C
test.foo
}
object Narrowed4 extends MayNarrow2 {
type T = C
override def test: T = new C {}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With