Basically, B overrides the type of property X to be
String
instead of
String?
because x is a
val
which means it is a
getter
only and so in that case B is allowed to `"refine" down the type of x to a subclass of
String?
(and it refined it to
String
) and so when C tries to override B it only sees the
String
type.
Think about it this way: B is stating that it can A's constraint of returning a value that fits
String?
whenever x's getter is called, but in addition B is also now adding a constraint onto itself that is saying "and I promise that whenever x is called the value returned will be of type String` and it is allowed to do that because again it fits A's constraints. So now any class that uses a B object expects x to return a value of type
String
. Then, C comes around and is trying to override x to always return null, which is of type
Nothing?
. The issue with that is that C extends B, and so it is trying to return
Nothing?
for a field that promises it returns
String
. Now, if C was allowed to return null here, then someone who is using a B object that in reality is actually a C object would run into unexpected NPEs because, to the user's knowledge, B.x
never returns null, or anything that isn't of type
String
Another exaggerated example would be having A.x actually be of type
Any?
, and then B.x being equal to
"x"
or something, which makes it of type String, and then having C.x set to
42
. Now that wouldn't compile for exactly the same reason because even though A.x is defined as Any?, C is forced to respect the constraints of B because it extends B.