In Kotlin, I'm trying to have a mutable generic value in sealed class A, and a mutable generic Number value in sealed class B with a mutable Long/... value in final C/...; but whenever I change A and B's values to "var" it gives me: Var-property type is T, which is not a type of overridden public open var value: Number
.
Example:
sealed class Data<T>(
open val name: String,
open var value: T
)
sealed class NumberData<T: Number>(
override val name: String,
override var value: T
): Data<Number>(name, value)
class TextData(name: String, override var value: String) : Data<String>(name, value)
class LongData(name: String, override var value: Long) : NumberData<Long>(name, value)
// class ...Data(name: String, value: ...) : Data<...>(name, value)
fun main() {
val dataSet = setOf<Data<*>>(TextData("a1", "hello, world"), LongData("a2", 50024))
val translate = "a1 a2 c3"
// Goal: Translate "a1 a2 c3" into a string using dataSet values while ignoring those that aren't in the set.
// "a1 a2 c3" -> "a1's value a2's value"
buildSet {
val map = dataSet.associateBy { it.name }
for(name in translate.split(" ")) {
if(map.containsKey(name)) {
add(map[name]!!.apply { value = 5 }) // "val" cannot be reassigned
// cannot change val to var in Data as NumberData will give a compiler error:
// Var-property type is T, which is not a type of overridden public open var value: Number
}
}
}.joinToString(" ") { it.value.toString() }
}
Why is this, and how would I go about fixing it?
CodePudding user response:
NumberData<T>
inherits from Data<Number>
, not Data<T>
, so the type of value
is expected to be Number
, not T
.
It is possible to override a read-only property with a subtype, e.g.
open class Foo(
open val x: Number
)
open class Bar(
override val x: Int
): Foo(x)
This is the case when you do override val value: T
. T
is a subtype of Number
.
This is because someone accessing an instance of Bar
via Foo
can still access x
without breaking anything.
val f: Foo = Bar(100)
val number: Number = f.x // 100 is a Number, so everything is fine
However, if x
can be set, this would break:
val f: Foo = Bar(100)
// Foo.x declares a setter that takes a Number. Double is a Number, so this should be possible
// But what this actually does though, is that it sets Bar.x at runtime, which is an Int!
f.x = 2.5
You probably meant for NumberData<T>
to inherit Data<T>
instead.
It is also not possible to set the value
of a Data<*>
. This is because you don't know what the exact type of Data
it is. Is it a Data<String>
? Data<Long>
? Or Data<SomethingElse>
? If you don't know that, how would you know that value
can take the value 5
?
To do what you're trying to do, you don't need to set the value
at all. You can just do:
val map = dataSet.associate { it.name to it.value }
val result = translate
.split(" ")
.mapNotNull { map[it]?.toString() }
.joinToString(" ")
println(result)