英文:
Kotlin Generics: why is the supertype of "SomeActor : DataActor<SomeData>" "DataActor<out SomeData>" and not "DataActor<SomeData>"?
问题
Line of Interest: 29: val actor: DataActor<out Data> = when ...
"out" 的添加使得调用以 Data 作为参数的函数变得不可能(例如第36行)。
使用此问题 question 我能够理解问题所在:
如果类型是 DataActor<Data>
,则可以将任何 Data 添加到任何 Actor 中。例如,可以将 Text
添加到 NumberActor
中。因为我只将由 SomeActor 生成的数据用作 SomeActor 的消费品,我知道这样做是安全的。
但问题没有回答的是:如何以惯用方式实现我想要的功能?
我唯一想到的没有未经检查的转换的解决方案是在 when
子句中执行所有操作,但在实际应用中,这将导致大量的代码重复。
interface Data
class Text(var text: String) : Data
class Number(var number: Int) : Data
interface DataActor<A: Data> {
fun add(element: A): Boolean;
fun get(index: Int): A
}
class TextActor : DataActor<Text> {
private val list = ArrayList<Text>()
override fun add(element: Text) = list.add(element)
override fun get(index: Int): Text = list[index]
}
class NumberActor : DataActor<Number> {
private val list = ArrayList<Number>()
override fun add(element: Number) = list.add(element)
override fun get(index: Int): Number = list[index]
}
enum class Selector {
Number,
Text
}
fun main() {
val selector = Selector.Number
// 为什么它的类型是 DataActor<out Data> 而不是 DataActor<Data>?
val actor = when(selector) {
Selector.Number -> NumberActor()
Selector.Text -> TextActor()
} // as DataActor<Data> // 丑陋的解决方案
val data = actor.get(0)
// 由于需要 DataActor<in Data> 或 DataActor<Data>,所以这是不可能的
actor.add(data)
}
英文:
I have build a minimal example to show my problem. I already know a ugly solution but I would like to find a better solution.
Line of Interest: 29: val actor: DataActor<out Data> = when ...
The addition of "out" makes it impossible to call functions that take Data as argument (e.g line 36).
With this question question I was able to understand what the problem is:
If the type would be DataActor<Data>
It would be possible to add any Data to any Actor. E.g it would be possible to add Text
to NumberActor
. Because I only use data produced by SomeActor as consumable for SomeActor I know it save to do.
But what the question does not answer: What's the idiomatic way to impl. my desired functionality?
The only solution without unchecked casts I can come up with is to do all actions inside the when
clause but in the real application this would lead to massive code duplication.
interface Data
class Text(var text: String) : Data
class Number(var number: Int) : Data
interface DataActor<A: Data> {
fun add(element: A): Boolean;
fun get(index: Int): A
}
class TextActor : DataActor<Text> {
private val list = ArrayList<Text>()
override fun add(element: Text) = list.add(element)
override fun get(index: Int): Text = list[index]
}
class NumberActor : DataActor<Number> {
private val list = ArrayList<Number>()
override fun add(element: Number) = list.add(element)
override fun get(index: Int): Number = list[index]
}
enum class Selector {
Number,
Text
}
fun main() {
val selector = Selector.Number
// Why is it from type DataActor<out Data> and not DataActor<Data>?
val actor = when(selector) {
Selector.Number -> NumberActor()
Selector.Text -> TextActor()
} // as DataActor<Data> // ugly solution
val data = actor.get(0)
// This is not possible as it needs DataActor<in Data> or DataActor<Data>
actor.add(data)
}
答案1
得分: 1
I think you explained you already understand the problem. These two classes do not have the same invariant supertype. They can only have a common covariant supertype.
So to solve this, you need to break out the code that maintains invariance into its own generic function. Within the generic function, T
is invariant. It doesn't care which T
it is, so the code is safe.
fun main() {
val selector = Selector.Number
val actor = when (selector) {
Selector.Number -> NumberActor()
Selector.Text -> TextActor()
}
doWork(actor)
}
fun <T: Data> doWork(actor: DataActor<T>) {
val data = actor.get(0)
actor.add(data)
}
This of course is based on your very simple example. This solution may not be feasible for more complicated cases, like if you were trying to transfer items between distinct instances of these classes.
英文:
I think you explained you already understand the problem. These two classes do not have the same invariant supertype. They can only have a common covariant supertype.
So to solve this, you need to break out the code that maintains invariance into its own generic function. Within the generic function, T
is invariant. It doesn't care which T
it is, so the code is safe.
fun main() {
val selector = Selector.Number
val actor = when(selector) {
Selector.Number -> NumberActor()
Selector.Text -> TextActor()
}
doWork(actor)
}
fun <T: Data> doWork(actor: DataActor<T>) {
val data = actor.get(0)
actor.add(data)
}
This of course is based on your very simple example. This solution may not be feasible for more complicated cases, like if you were trying to transfer items between distinct instances of these classes.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论