【问题标题】:In Scala how do I filter by reified types at runtime?在 Scala 中,如何在运行时按具体类型进行过滤?
【发布时间】:2014-05-03 00:22:21
【问题描述】:

我有一个包含不同子类型对象的 Scala 集合。

abstract class Base

class A extends Base

class B extends Base

val a1 = new A()
val a2 = new A()
val b = new B()
val s = List(a1, a2, b)

我想过滤掉所有A 对象或B 对象。如果我知道在编译时要过滤的对象,我可以轻松做到这一点。

s.filter(_.isInstanceOf[A]) // Give me all the As
s.filter(_.isInstanceOf[B]) // Give me all the Bs

如果我只知道要在运行时过滤的对象类型,我可以这样做吗?我想写一个这样的函数。

def filterType(xs:List[Base], t) = xs.filter(_.isInstanceOf[t])

其中t 表示我想要AB 类型的对象。

当然,由于类型擦除,我实际上不能这样写。有没有一种惯用的 Scala 方法可以使用类型标签来解决这个问题?我一直在阅读 Scala 类型标签文档和相关的 StackOverflow 帖子,但我无法弄清楚。

【问题讨论】:

    标签: scala type-erasure


    【解决方案1】:

    这已经出现过几次了。重复,有人吗?

    scala> trait Base
    defined trait Base
    
    scala> case class A(i: Int) extends Base 
    defined class A
    
    scala> case class B(i: Int) extends Base 
    defined class B
    
    scala> val vs = List(A(1), B(2), A(3))
    vs: List[Product with Serializable with Base] = List(A(1), B(2), A(3))
    
    scala> def f[T: reflect.ClassTag](vs: List[Base]) = vs collect { case x: T => x }
    f: [T](vs: List[Base])(implicit evidence$1: scala.reflect.ClassTag[T])List[T]
    
    scala> f[A](vs)
    res0: List[A] = List(A(1), A(3))
    

    【讨论】:

    • 赞成你,因为我没有意识到 match 可以利用 ClassTag,这真的很关键。
    【解决方案2】:

    类型擦除会破坏类型参数中的任何信息,但对象仍然知道它们属于哪个类。因此,我们无法过滤任意类型,但我们可以按类或接口/特征进行过滤。 ClassTagTypeTag 更可取。

    import scala.reflect.ClassTag
    
    def filterType[T: ClassTag](xs: List[Base]) = xs.collect {
      case x: T => x
    }
    

    我们可以这样使用:

    scala> filterType[B](s)
    res29: List[B] = List(B@42096939)
    
    scala> filterType[Base](s)
    res30: List[Base] = List(A@8dbc09c, A@625f8cc7, B@42096939)
    

    如果类型T 不是通用的,则此方法在运行时是安全的。如果有class C[T] extends Base,我们就不能安全地过滤C[String]

    【讨论】:

    • 看起来像@wingedsubmariner,有翅膀,尽管模式匹配器避免了 isInstance 测试。
    • 你是对的,@som-snytt。我已经清理了不再使用isInstanceasInstanceOf 的答案。
    • @wingedsubmariner 这是一个很好的解决方案;我试图通过用[_ >: T] 替换[Base] 来为函数签名添加类型边界,这个想法是在编译时捕获原则上永远不会成功的过滤操作(假设原始列表人口没有被破坏) .但是编译器不会拒绝它们。对此有何想法?
    • @satyagraha 它可能只是选择Any 作为它的T,然后让它总是进行类型检查。我不确定如何轻松地做你想做的事。
    猜你喜欢
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-10
    • 2015-11-17
    相关资源
    最近更新 更多