如何读取扩展Any而不是AnyRef的Scala对象的类



我有一个异构列表,如下所示:

val l = List(1, "One", true)

,我需要通过只提取属于给定类的对象来过滤它的对象。为此,我编写了一个非常简单的方法:

def filterByClass[A](l: List[_], c: Class[A]) =
  l filter (_.asInstanceOf[AnyRef].getClass() == c)

请注意,为了避免这个编译问题,我必须向AnyRef添加显式转换:

error: type mismatch;
found   : _$1 where type _$1
required: ?{val getClass(): ?}
Note that implicit conversions are not applicable because they are ambiguous:
both method any2stringadd in object Predef of type (x: Any)scala.runtime.StringAdd
and method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A]
are possible conversion functions from _$1 to ?{val getClass(): ?}
l filter (_.getClass() == c)

然而,通过这种方式调用:

filterByClass(l, classOf[String])

按预期返回:

List(One)

,但当然同样不工作,例如,对于Int,因为它们扩展Any而不是AnyRef,所以通过调用:

filterByClass(l, classOf[Int])

结果就是空List。

是否有一种方法使我的filterByClass方法工作,甚至与Int,布尔和所有其他类扩展Any?

collect方法已经做了您想要的。例如,要收集集合中的所有Int,您可以写入

xs collect { case x: Int => x }

这当然只在硬编码类型时有效,但由于原语的处理方式与引用类型不同,因此这样做实际上更好。您可以使用一些类型类来简化您的工作:

case class Collect[A](collect: PartialFunction[Any,A])
object Collect {
  implicit val collectInt: Collect[Int] = Collect[Int]({case x: Int => x})
  // repeat for other primitives
  // for types that extend AnyRef
  implicit def collectAnyRef[A <: AnyRef](implicit mf: ClassManifest[A]) =
    Collect[A]({ case x if mf.erasure.isInstance(x) => x.asInstanceOf[A] })
}
def collectInstance[A : Collect](xs: List[_ >: A]) =
  xs.collect(implicitly[Collect[A]].collect)

然后你可以使用它甚至不传递一个Class[A]实例:

scala> collectInstance[Int](l)
res5: List[Int] = List(1)
scala> collectInstance[String](l)
res6: List[String] = List(One)

Using isInstanceOf:

scala> val l = List(1, "One", 2)
l: List[Any] = List(1, One, 2)
scala> l . filter(_.isInstanceOf[String])
res1: List[Any] = List(One)
scala> l . filter(_.isInstanceOf[Int])
res2: List[Any] = List(1, 2)

编辑:正如OP所要求的,这里是另一个版本,它将签入一个方法。我找不到使用isInstanceOf的方法,所以我改变了实现,使用ClassManifest:

def filterByClass[A](l: List[_])(implicit mf: ClassManifest[A]) =
  l.filter(mf.erasure.isInstance(_))

一些使用场景:

scala> filterByClass[String](l)
res5: List[Any] = List(One)
scala> filterByClass[java.lang.Integer](l)
res6: List[Any] = List(1, 2)
scala> filterByClass[Int](l)
res7: List[Any] = List()
从上面可以看出,这个解决方案不适用于Scala的Int类型。

List[Any]中元素的类永远不会是classOf[Int],所以这是预期的行为。你的假设显然出乎意料,但很难给你一个更好的方法,因为正确的方法是"不要那样做"。

你认为关于异构列表的成员的类可以说些什么?也许这能说明问题。我很好奇为什么你认为java做得更好。

scala> def f[T: Manifest](xs: List[T]) = println(manifest[T] + ", " + manifest[T].erasure)
f: [T](xs: List[T])(implicit evidence$1: Manifest[T])Unit
scala> f(List(1))
Int, int
scala> f(List(1, true))
AnyVal, class java.lang.Object
scala> f(List(1, "One", true))
Any, class java.lang.Object

这对我很有效。这是你想要的吗?

scala> val l = List(1, "One", true)
l: List[Any] = List(1, One, true)
scala> l filter { case x: String => true; case _ => false }
res0: List[Any] = List(One)
scala> l filter { case x: Int => true; case _ => false }
res1: List[Any] = List(1)
scala> l filter { case x: Boolean => true; case _ => false }
res2: List[Any] = List(true)

尽管我的解决方案可能没有这个优雅,但我发现我的解决方案更快更容易。我刚刚定义了这样一个方法:

private def normalizeClass(c: Class[_]): Class[_] =
  if (classOf[AnyRef].isAssignableFrom((c))) c
  else if (c == classOf[Int]) classOf[java.lang.Integer]
  // Add all other primitive types
  else classOf[java.lang.Boolean]

所以在我以前的filterByClass方法中使用它,如下所示:

def filterByClass[A](l: List[_], c: Class[A]) =
  l filter (normalizeClass(c).isInstance(_))

调用:

filterByClass(List(1, "One", false), classOf[Int])

就返回

List(1)

最后,这个问题简化为在原语和相应的盒装类型之间找到映射。也许scala.reflect.Invocation可以提供帮助(不包括在2.8.0的最终版本中),特别是getAnyValClass函数(这里略有编辑)

def getAnyValClass(x: Any): java.lang.Class[_] = x match {
  case _: Byte    => classOf[Byte]
  case _: Short   => classOf[Short]
  case _: Int     => classOf[Int]
  case _: Long    => classOf[Long]
  case _: Float   => classOf[Float]
  case _: Double  => classOf[Double]
  case _: Char    => classOf[Char]
  case _: Boolean => classOf[Boolean]
  case _: Unit    => classOf[Unit]
  case x@_        => x.asInstanceOf[AnyRef].getClass
}

使用这个函数,过滤器就像

一样简单
def filterByClass[T: Manifest](l:List[Any]) = {
  l filter (getAnyValClass(_) == manifest[T].erasure)
}

,调用是:

filterByClass[Int](List(1,"one",true))

相关内容

  • 没有找到相关文章

最新更新