Karlo Lozovina
07/29/2021, 7:07 PMabstract class A {
open val x: String? = null
}
sealed class B : A() {
override val x = "x"
}
class C : B() {
override val x = null
}
If I add a type annotation on B.x
of String?
, then it works...randomcat
07/29/2021, 7:09 PMB.x
is String
, not String?
randomcat
07/29/2021, 7:09 PMC.x
is Nothing?
, which is not a subtype of String
Karlo Lozovina
07/29/2021, 7:11 PMB.x
? Why isn't it's type the same as A.x
?randomcat
07/29/2021, 7:12 PMKarlo Lozovina
07/29/2021, 7:15 PMA
... thanks, it's a bit surprising...randomcat
07/29/2021, 7:16 PMstreetsofboston
07/29/2021, 7:19 PMrandomcat
07/29/2021, 7:21 PMrandomcat
07/29/2021, 7:21 PMString
streetsofboston
07/29/2021, 7:23 PMstreetsofboston
07/29/2021, 7:25 PMrandomcat
07/29/2021, 7:27 PMstreetsofboston
07/29/2021, 7:49 PMYoussef Shoaib [MOD]
07/30/2021, 5:40 PMString
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.Karlo Lozovina
07/31/2021, 4:01 PMB
doesn't specify a type explicitly of overriden field (x
in this case), then it's type is exactly the type specified in the parent class.
Once one writes out all the types explicitly (as Kotlin infers them), it's obvious one can't assign null
to C.x
...