Scala 使用协变类型的私有可变集合
我有一个协变的Scala类型Scala 使用协变类型的私有可变集合,scala,covariant,Scala,Covariant,我有一个协变的Scala类型东西[+B]。该实现使用内部可变队列: private val队列:异步队列[B]() AsyncQueue是一个定制的可变队列实现,具有我无法在不可变版本中轻松实现的特殊属性。因为它是可变的,所以AsyncQueue是不变的。所以我不能在我的协变类型中使用它 由于queue是私有的,我可以保证我的代码的正确性:例如,我不会尝试将queue分配给queue[Any]类型的引用。如果不使用强制转换,我如何使东西在B中保持协变 (使用强制转换的解决方案是声明一个Async
东西[+B]
。该实现使用内部可变队列:
private val队列:异步队列[B]()
AsyncQueue是一个定制的可变队列实现,具有我无法在不可变版本中轻松实现的特殊属性。因为它是可变的,所以AsyncQueue是不变的。所以我不能在我的协变类型中使用它
由于queue
是私有的,我可以保证我的代码的正确性:例如,我不会尝试将queue
分配给queue[Any]
类型的引用。如果不使用强制转换,我如何使东西在B
中保持协变
(使用强制转换的解决方案是声明一个AsyncQueue[Object]
并将对象强制转换为enqueue/dequeue,这非常难看。)
ETA:我理解类型协变,我理解为什么我不能声明协变类型的AsyncQueue,或者使AsyncQueue本身协变。我的问题是如何设计此代码以避免到处使用强制类型转换。如果B
在Thing[+B]
中是协变的,那么您将无法使B
在Thing
中处于逆变位置,即
def put(b:B) {...} // will fail to compile, can't use a covariant type in this position
但是可以为东西
创建两个接口,一个在协变位置使用类型,另一个在逆变位置使用类型,如下所示:
trait ThingProduce[+B] {
def get: B
}
trait ThingConsume[-B] {
def put(b: B)
}
class Thing[B] extends ThingConsume[B] with ThingProduce[B] {
private val queue = new scala.collection.mutable.Queue[B]
def put(b: B) {queue.enqueue(b)}
def get: B = queue.dequeue
def both(b: B): B = ???
}
因此,对于类层次结构:
class Animal
class Mammal extends Animal
class Dog extends Mammal
可以做到以下几点:
val mammalThing: Thing[Mammal] = new Thing[Mammal]{}
val dogConsumer: ThingConsume[Dog] = mammalThing
val animalProducer: ThingProduce[Animal] = mammalThing
但不是:
val dogConsumer: ThingConsume[Dog] = animalProducer
//or
val animalProducer: ThingProduce[Animal] = dogConsumer
因此,事物[B]
既可以看作是共变的,也可以看作是逆变的,但仅限于某些成员。您需要@uncheckedVariance
:
import scala.annotation.unchecked.uncheckedVariance
class A[T] {}
class B[+T] {
val a: A[T @uncheckedVariance] = null
}
甚至Scala标准库也利用了@uncheckedVariance
,特别是允许不变的可变集合从协变特征继承。你可以通过将其设置为私有[此]
,使你的成员不受方差检查的影响
预料之中
scala> class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
<console>:12: error: covariant type A occurs in invariant position in type => scala.collection.mutable.ArrayBuffer[A] of value as
class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
及
这听起来像是导致java泛型设计者将差异声明放在对象使用点而不是声明位置的确切用例。Thing
上使用queue
的方法是什么?AsyncQueue
是一个基于未来的有界队列。一个方法调用queue.enqueue
,它返回一个未来,该未来在项目排队时完成(在队列中没有空间时等待)。另一个调用queue.dequeue
,它返回一个未来,该未来在项目出列时完成(队列为空时等待)。这些方法让我可以构建一个基于未来的状态机,在队列已满时暂停。进入队列的B
s从哪里来?您能否举例说明您必须在何处使用强制转换。B
s来自用户(即通过Thing
的公共API),并被推到队列中。如果我声明了一个AsyncQueue[AnyRef]
,那么我必须将退出队列的值从AnyRef
转换回B
。我只能接受一个答案,但我也很高兴向您学习。谢谢
scala> class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
<console>:12: error: covariant type A occurs in invariant position in type => scala.collection.mutable.ArrayBuffer[A] of value as
class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
scala> class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { private[this] val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
defined class Thingie
scala> class X
defined class X
scala> val xs = new Thingie[X]
xs: Thingie[X] = Thingie@49f5c307
scala> xs.next
res1: X = X@4816c290