我是scala的新手并遇到了以下问题:
我想获得一个仅包含特定类型元素的现有集合的子集合。以下作品:
class C(val name : String)
class D(name : String) extends C(name) { }
val collection = Set[C](new C("C1"),new D("D1"),new C("C2"),new D("D2"))
collection.collect{case d : D => d}.size must be === 2 // works
但是当我尝试使用方法“onlyInstancesOf [Type]”扩展集合类时,这不起作用。首先是我的实施:
object Collection {
implicit def extendScalaCollection[E](coll : Traversable[E]) = new CollectionExtension[E](coll)
}
class CollectionExtension[E](coll : Traversable[E]) {
def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = {
coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
}
}
所以当我使用这个扩展并执行:
collection.onlyInstancesOf[D].size must be === 2
我收到一个错误,.size返回4而不是2.此外,我检查过,结果实际上包含C1和C2,但它不应该。
当我这样做时:
collection.onlyInstancesOf[D].foreach(e => println(e.name))
我得到例外:
java.lang.ClassCastException: CollectionsSpec$$anonfun$1$C$1 cannot be cast to CollectionsSpec$$anonfun$1$D$1
很明显,结果集仍然包含应该过滤掉的元素。
我不明白为什么会发生这种情况,有人可以解释一下吗?
编辑: Scala:Scala代码运行器版本2.8.0.final
答案 0 :(得分:10)
注意编译器警告,并添加-unchecked scala命令行选项。
M:\>scala -unchecked
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) Client VM, Java 1.6.0_21)
.
Type in expressions to have them evaluated.
Type :help for more information.
scala> class CollectionExtension[E](coll : Traversable[E]) {
|
| def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = {
| coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
| }
| }
<console>:8: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure
coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
^
defined class CollectionExtension
警告意味着编译器可以做的最好的事情相当于:
coll.collect({case special : AnyRef => special}).asInstanceOf[Traversable[_]]
有关类型擦除的更详细说明以及使用清单解决方法,请参阅:
https://stackoverflow.com/questions/tagged/type-erasure+scala
答案 1 :(得分:6)
正如其他人所指出的那样,清单可以拯救你。这里有一个例子,说明如何将自己限制在非基元,并假设我们不想在我们的集合中存储清单,而是在现场使用反射来解决问题:
class CollectionExtension[E <: AnyRef](coll : Traversable[E]) {
def onlyInstancesOf[SpecialE <: E](implicit mf : Manifest[SpecialE]) : Traversable[SpecialE] = {
coll.collect({
case special if mf.erasure.isAssignableFrom(special.getClass) => special
}).asInstanceOf[Traversable[SpecialE]]
}
}
在这里它正在发挥作用:
scala> val ce = new CollectionExtension(List(Some(1),Some(5),"This","Fox"))
ce: CollectionExtension[java.lang.Object] = CollectionExtension@1b3d4787
scala> val opts = ce.onlyInstancesOf[Some[_]]
opts: Traversable[Some[_]] = List(Some(1), Some(5))
scala> val strings = ce.onlyInstancesOf[String]
strings: Traversable[String] = List(This, Fox)
答案 2 :(得分:4)
Scala在JVM上运行,不幸的是在运行时删除了类型参数:http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure。在第一个示例中,您将类型赋予非擦除位置,因此运行时代码可以进行比较。在第二个示例中,SpecialE
类型被删除,因此代码将返回所有内容。
您可以使用scala的清单重新获得类型擦除所丢失的一些信息:
import scala.reflect.ClassManifest
class CollectionsExtension[E <: AnyRef](coll : Traversable[E]) {
def onlyInstancesOf[SpecialE <: E](implicit m : Manifest[SpecialE]) : Traversable[SpecialE] = {
coll.collect({case e if (ClassManifest.singleType(e) <:< m) => e}).asInstanceOf[Traversable[SpecialE]]
}
}
答案 3 :(得分:3)
警告说:
<console>:14: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure
coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
让我们看看collect
:
def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
for (x <- this) if (pf.isDefinedAt(x)) b += pf(x)
b.result
}
请注意,这里没有模式匹配。这是根本区别 - 当您编写“collection.collect{case d : D => d}
”时,编译器确切地知道您正在谈论的类型:D
。
另一方面,当您编写coll.collect({case special : SpecialE => special})
时,编译器不知道什么类型SpecialE
,因为SpecialE
只是一个类型参数。因此,它无法生成知道SpecialE
是什么的代码,并且在运行时不再有SpecialE
- 字节码只使用java.lang.Object
。