software - scala vs java
Scala asInstanceOf con tipos parametrizados (5)
Me gustaría escribir una función que envíe al tipo A, donde A puede ser, por ejemplo, List [Int], o un tipo parametrizado más complicado como Map [Int, List [Int]].
def castToType[A](x: Any): A = {
// throws if A is not the right type
x.asInstanceOf[A]
}
En este momento, debido al borrado de tipo (creo), el código funciona feliz incluso cuando el tipo no es correcto. El error solo se manifiesta en el acceso, con ClassCastException.
val x = List(1, 2, 3)
val y = castToType[List[String]](x)
y(0) --> throws java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
¿Hay alguna manera de usar manifiestos para que esto funcione correctamente? ¡Gracias!
Considera esta solución:
trait -->[A, B] {
def ->(a: A): B
}
implicit val StringToInt = new -->[String, Int] {
def ->(a: String): Int = a.toInt
}
implicit val DateToLong = new -->[java.util.Date, Long] {
def ->(a: java.util.Date): Long = a.getTime
}
def cast[A,B](t:A)(implicit ev: A --> B):B= ev.->(t)
La ventaja es que:
- Es tipo seguro: el compilador te dirá si el tipo no se puede convertir
- Puede definir reglas de transmisión proporcionando implícitos adecuados
Ahora puedes usarlo así que:
scala> cast(new java.util.Date())
res9: Long = 1361195427192
scala> cast("123")
res10: Int = 123
EDITAR
He pasado un tiempo y escribí este código avanzado. Primero déjame mostrarte cómo usarlo:
scala> "2012-01-24".as[java.util.Date]
res8: java.util.Date = Tue Jan 24 00:00:00 CET 2012
scala> "2012".as[Int]
res9: Int = 2012
scala> "2012.123".as[Double]
res12: Double = 2012.123
scala> "2012".as[Object] // this is not working, becouse I did not provide caster to Object
<console>:17: error: could not find implicit value for parameter $greater: -->[String,Object]
"2012".as[Object]
^
¿Bastante agradable? Ver la magia scala:
trait -->[A, B] {
def ->(a: A): B
}
implicit val StringToInt = new -->[String, Int] {
def ->(a: String): Int = a.toInt
}
implicit val StringToDate = new -->[String, java.util.Date] {
def ->(a: String): java.util.Date = (new java.text.SimpleDateFormat("yyyy-MM-dd")).parse(a)
}
implicit val StringToDouble = new -->[String, Double] {
def ->(a: String): Double = a.toDouble
}
trait AsOps[A] {
def as[B](implicit > : A --> B): B
}
implicit def asOps[A](a: A) = new AsOps[A] {
def as[B](implicit > : A --> B) = > ->(a)
}
De hecho, es correcto: el borrado de tipos significa que no puede "emitir" de forma tal que se distinga entre List[Int]
y List[String]
, por ejemplo. Sin embargo, puedes mejorar el reparto que estás realizando, por lo que A
se borra de tal forma que no puedes distinguir entre un Int
y una String
:
def cast[A](a : Any) = a.asInstanceOf[A]
//... is erased to
def erasedCast(a : Any) = a.asInstanceOf[Any]
Lo que necesita son genéricos reificados , utilizando manifiestos
def cast[A <: AnyRef : Manifest](a : Any) : A
= manifest[A].erasure.cast(a).asInstanceOf[A]
Mientras que el elenco final se borra en AnyRef
, al menos debe tener la instancia de Class[_]
correcta ( manifest.erasure
) para obtener el tipo de nivel superior correcto. En acción:
scala> cast[String]("Hey")
res0: String = Hey
scala> cast[java.lang.Integer]("Hey")
java.lang.ClassCastException
at java.lang.Class.cast(Class.java:2990)
at .cast(<console>:7)
at .<init>(<console>:9)
scala> cast[List[String]](List("Hey"))
res2: List[String] = List(Hey)
scala> cast[List[Int]](List("Hey"))
res3: List[Int] = List(Hey)
Mi consejo es no utilizar la reflexión anidada para decidir si el objetivo era realmente una List[Int]
: esto generalmente no es factible. ¿Para qué debería regresar lo siguiente?
cast[List[Int]](List[AnyVal](1, 2))
Lamentablemente, esto en una limitación inherente de asInstanceOf
. De hecho, me sorprende ver al scaladoc mencionarlo en details :
Tenga en cuenta que el éxito de un elenco en el tiempo de ejecución es la semántica de borrado del módulo Scala. Por lo tanto, la expresión
1.asInstanceOf[String]
lanzará unaClassCastException
en tiempo de ejecución, mientras que la expresiónList(1).asInstanceOf[List[String]]
no lo hará. En el último ejemplo, dado que el argumento de tipo se borra como parte de la compilación, no es posible verificar si los contenidos de la lista son del tipo solicitado.
Si lo que más le preocupa es fallar rápidamente en el reparto incorrecto de traversable, que probablemente sea el problema principal cuando recupere material de su interfaz DB / memcached, estaba jugando forzando un lanzamiento de cabezales para objetos transitables:
def failFastCast[A: Manifest, T[A] <: Traversable[A]](as: T[A], any: Any) = {
val res = any.asInstanceOf[T[A]]
if (res.isEmpty) res
else {
manifest[A].newArray(1).update(0, res.head) // force exception on wrong type
res
}
}
En un simple ejemplo, funciona:
scala> val x = List(1, 2, 3): Any
x: Any = List(1, 2, 3)
scala> failFastCast(List[String](), x)
java.lang.ArrayStoreException: java.lang.Integer
[...]
scala> failFastCast(List[Int](), x)
res22: List[Int] = List(1, 2, 3)
Pero no en uno más complejo:
val x = Map(1 -> ("s" -> 1L)): Any
failFastCast(Map[Int, (String, String)](), x) // no throw
Me pregunto si hay una forma de profundizar de forma recursiva en A para seguir lanzando hasta que no haya más parámetros de tipo ...
Puede usar Typeable''s typeable de Miles Sabin:
Tipo de conversión utilizando el parámetro de tipo
Maneja el borrado en muchos casos, aunque solo específicos:
scala> import shapeless._; import syntax.typeable._
import shapeless._
import syntax.typeable._
scala> val x = List(1, 2, 3)
x: List[Int] = List(1, 2, 3)
scala> val y = x.cast[List[String]]
y: Option[List[String]] = None
Para ver el conjunto de casos que maneja, puede consultar su fuente:
https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala
Sí, el problema ocurre debido al borrado de tipo. Si intentas
val x = List(1,2,3)
val y = castToType[Int](x)
La excepción se produce de inmediato, como se esperaba. Lo mismo ocurre cuando intentas lanzar a Array[String]
o incluso Array[Int]
.
No creo que pueda crear un convertidor de tipo genérico que funcione dentro de los tipos de colecciones y otros objetos. Tendrá que crear un convertidor para cada tipo de objeto. Por ejemplo:
def castToType[A](x: List[A]) = x.map(i => i.asInstanceOf[A])