我有一个异构列表,如下所示:
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))