Reputation: 23814
I found the following code in the Kotlin forum and it works fine.
sealed class JsonValue<out T>(val value: T) {
class JsonString(value: String) : JsonValue<String>(value)
class JsonBoolean(value: Boolean) : JsonValue<Boolean>(value)
class JsonNumber(value: Number) : JsonValue<Number>(value)
object JsonNull : JsonValue<Nothing?>(null)
class JsonArray<V>(value: Array<V>) : JsonValue<Array<V>>(value)
class JsonObject(value: Map<String, Any?>) : JsonValue<Map<String, Any?>>(value)
override fun toString(): String = value.toString()
}
fun main() {
var pi: JsonValue<Any?>
pi = JsonValue.JsonString("pi"); println (pi)
pi = JsonValue.JsonNumber(3.14); println (pi)
pi = JsonValue.JsonNull; println (pi)
}
But I do not understand why it uses out T
.
An answer to a question about out
in general states:
out T
[...] means functions can returnT
but they can't takeT
as arguments.
in T
[...] means functions can takeT
as arguments but they can't returnT
.
If I take a look at the above code, I can see many constructors (functions), which take T
(the value
) as an argument. And I see no function which returns T
. So my inital impression was: this must be a typo, it should be in T
. But it does not even compile with in T
.
Why is it necessary to use out T
, although the type goes into the constructor?
Upvotes: 1
Views: 75
Reputation: 271070
The constructor doesn't really count :) Only instance members matter - things that you can do to instances of JsonValue
.
As explained in the linked answer, the whole idea of (declaration-site) covariance is that you are allowed to implicitly convert an instance of e.g. JsonValue<String>
to JsonValue<Any?>
if the type JsonValue<T>
satisfies some requirements. One of the requirements is that JsonValue<T>
should not have any functions that take in any T
s*, because if it did, weird things like this would happen:
val x: JsonValue<Any?> = JsonString("foo")
x.giveMeSomeT(123)
x
at runtime holds an instance of JsonString
, but the giveMeSomeT
method in JsonString
would expect a String
, not an Int
, but as far as the compiler is concerned, x
is a JsonValue<Any?>
, so this should compile, and bad things would happen at runtime.
So this is why having a function that takes in T
s stops you from marking JsonValue
as out T
. However, having a constructor that takes in a T
is not problematic at all, since situations like the above cannot happen with just a constructor.
And I see no function which returns
T
In fact, the getter of value
returns T
. Also note that you do not need something that returns T
to in order to say out T
. You just need to to have nothing that takes in T
s. This is vacuously valid for example:
class Foo<out T>
* More accurately and generally, whenever I say "take in any T
s", it should be "have T
in an 'in' position", and whenever I say "return a T
", it should be "have T
in an 'out' position". This is to account for T
s being used as the type argument of other generic types.
Upvotes: 3