Scala 集合按类型过滤

发布于 2024-09-18 00:55:05 字数 1274 浏览 7 评论 0原文

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

im new to scala and ran into the following problem:

I want to get a subcollection of an existing collection that only contains elements of a specific type. The following works:

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

But when i try to extend the collection classes with a method "onlyInstancesOf[Type]" this does not work. First my implementation:

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]]
    }
}

So when i use this extension and execute:

collection.onlyInstancesOf[D].size must be === 2

I get an error that .size returned 4 and not 2. Also, i checked, the result actually contains C1 and C2 though it should not.

When i do:

collection.onlyInstancesOf[D].foreach(e => println(e.name))

I get the exception:

java.lang.ClassCastException: CollectionsSpec$anonfun$1$C$1 cannot be cast to CollectionsSpec$anonfun$1$D$1

So obviously the resulting set still contains the elements that should have been filtered out.

I dont get why this happens, can anyone explain this?

Edit:
Scala: Scala code runner version 2.8.0.final

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

悲歌长辞 2024-09-25 00:55:05

注意编译器警告,并添加 -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

Pay attention to the compiler warnings, and add -unchecked your scala command line options.

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

The warning means that the best the compiler can do is equivalent to:

coll.collect({case special : AnyRef => special}).asInstanceOf[Traversable[_]]

For a more detailed explanation of type erasure, and ways you can work around it with Manifests, see:

https://stackoverflow.com/questions/tagged/type-erasure+scala

凉薄对峙 2024-09-25 00:55:05

正如其他人指出的那样,清单可以拯救您。下面是一个示例,说明如何将自己限制为非基元,并假设我们不想在集合中存储清单,而是当场使用反射来解决问题:

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)

As others have pointed out, manifests can rescue you. Here's an example of how, restricting ourselves to non-primitives, and assuming we don't want to store manifests in our collections but instead use reflection on the spot to figure things out:

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]]
  }
}

and here it is in action:

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)
他夏了夏天 2024-09-25 00:55:05

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]]
  }
}

Scala runs on the JVM, which unfortunately erases type parameters at runtime: http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure. In your first example, you give the type in a non-erased position and so the runtime code can do the comparison. In the second example, the SpecialE type is erased, and hence the code will return everything.

You can use scala's Manifests to regain some of the information lost by type erasure:

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]]
  }
}
自由如风 2024-09-25 00:55:05

正如警告所说:

<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({casespecial : SpecialE =>special}) 时,编译器不知道 SpecialE 是什么类型,因为 < code>SpecialE 只是一个类型参数。因此它无法生成知道 SpecialE 是什么的代码,并且在运行时,不再有 SpecialE —— 字节码只使用 java.lang .对象

As the warning say:

<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]]

Let's see the implementation of 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
}

Note that there's no pattern matching in here. This is the fundamental difference -- when you write "collection.collect{case d : D => d}" the compiler knows exactly what type you are talking about: D.

On the other hand, when you write coll.collect({case special : SpecialE => special}), the compiler doesn't know what type SpecialE, because SpecialE is just a type parameter. So it can't generate code that knows what SpecialE is, and, at run-time, there's no SpecialE anymore -- the bytecode just uses java.lang.Object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文