2011-04-10 8 views
6

que tiene una lista heterogénea como la siguiente:¿Cómo se lee la clase de un objeto Scala que amplía Any pero no AnyRef?

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

y necesito filtrar sus objetos extrayendo sólo los que pertenecen a una clase dada. Para este propósito escribí un método muy simple como esto:

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

Tenga en cuenta que estoy obligado a añadir la conversión explícita a AnyRef con el fin de evitar este problema compilación:

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) 

Sin embargo, en esta forma la invocación de:

filterByClass(l, classOf[String]) 

retornos como se esperaba:

List(One) 

pero por supuesto el mismo no funciona, por ejemplo, con Int ya que se extiende Cualquier pero no AnyRef, por lo que mediante la invocación:

filterByClass(l, classOf[Int]) 

el resultado es simplemente la lista vacía.

¿Hay alguna manera de hacer que mi método filterByClass funcione incluso con Int, Boolean y todas las otras clases extendiendo Any?

+2

¿Sería 'filterByClass (l, classof [java.lang.Integer])' ser aceptable? –

+0

¡Qué horrible estructura de datos para trabajar! ¿Cómo terminaste con tal cosa? –

+0

Estoy bastante sorprendido también. Debe haber sido un error muy molesto! Sin embargo, tener un bloque de coincidencia predeterminado para manejar primitivas Java podría ayudar. – Ajay

Respuesta

0

A pesar de mi solución podría ser menos elegante que this one encuentro de minas más rápido y más fácil. Me acaba de definir un método como este:

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] 

Así mediante el uso en mi primer método filterByClass como sigue:

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

la invocación de:

sólo devuelve

List(1) 

como esperaba ed.

0

Esto funcionó para mí. ¿Es esto lo que quieres?

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) 
+0

Eso no responde mi pregunta: ¿cómo podría escribir el método filterByClass? Necesito un genérico que funcione con cualquier clase. –

13

El método collect ya hace lo que quiere. Por ejemplo, para recoger todas Int s en una colección que podría escribir

xs collect { case x: Int => x } 

Por supuesto, esto sólo funciona cuando el tipo hardcode sino como primitivas se manejan de manera diferente de los tipos de referencia que en realidad es mejor que lo haga. Usted puede hacer su vida más fácil con algunas clases de tipo:

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) 

continuación, puede utilizar sin siquiera pasar un Class[A] ejemplo:

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

scala> collectInstance[String](l) 
res6: List[String] = List(One) 
+0

Esa es una buena solución. Gracias. Debo decir que estoy un poco sorprendido de que necesite algo tan detallado para realizar una tarea aparentemente trivial, y un poco decepcionado por el hecho de que Scala, a pesar de sus afirmaciones, trata, al menos en este caso, tipos primitivos de una manera aún menos uniforme y transparente que Java. –

3

Usando 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) 

editar: Como solicitó OP, aquí hay otra versión que mueve el cheque en un método. No pude encontrar una manera de utilizar isInstanceOf y por eso cambió la puesta en práctica de utilizar un ClassManifest:

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

Algunos escenarios de uso:

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() 

Como se puede ver arriba, esta solución no hace trabajar con el tipo Int de Scala.

+0

Eso no responde mi pregunta: ¿cómo podría escribir el método filterByClass? Necesito un genérico que funcione con cualquier clase. –

+0

Actualizaré mi respuesta en consecuencia. – Jawher

1

La clase de un elemento en una Lista [Cualquiera] nunca es classOf [Int], por lo que se comporta como se esperaba.Sus suposiciones aparentemente dejan esto inesperado, pero es difícil darle una mejor manera porque la manera correcta es "no hacer eso".

¿Qué piensas que se puede decir acerca de las clases de los miembros de una lista heterogénea? Quizás esto sea ilustrativo. Tengo curiosidad de cómo crees que Java lo hace mejor.

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 
0

Al final, este problema se reduce para encontrar un mapa entre una primitiva y el tipo de caja correspondiente. Tal vez una ayuda puede llegar de scala.reflect.Invocation (no incluido en la versión final 2.8.0), la función getAnyValClass en particular (en este caso ligeramente editado)

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 [email protected]_  => x.asInstanceOf[AnyRef].getClass 
} 

Con esta función del filtro es tan fácil como

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

y la invocación es:

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