4

我是 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

4

4 回答 4

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

于 2010-09-07T16:04:31.373 回答
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)
于 2010-09-07T16:22:47.043 回答
4

Scala 在 JVM 上运行,不幸的是它会在运行时擦除类型参数:http ://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure 。在您的第一个示例中,您将类型放在非擦除位置,因此运行时代码可以进行比较。在第二个示例中,SpecialE类型被擦除,因此代码将返回所有内容。

您可以使用 scala 的 Manifests 来重新获得因类型擦除而丢失的一些信息:

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]]
  }
}
于 2010-09-07T16:08:00.460 回答
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}),编译器不知道 type是什么SpecialE,因为SpecialE它只是一个类型参数。所以它不能生成知道是什么SpecialE的代码,而且在运行时,SpecialE不再有——字节码只使用java.lang.Object.

于 2010-09-07T16:09:28.537 回答