[英]Scala asInstanceOf with parameterized types
我想编写一个转换为类型 A 的 function,其中 A 可以是例如 List[Int],或更复杂的参数化类型,例如 Map[Int, List[Int]]。
def castToType[A](x: Any): A = {
// throws if A is not the right type
x.asInstanceOf[A]
}
现在,由于类型擦除(我相信),即使类型不正确,代码也能正常工作。 该错误仅在访问时出现,并带有 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
有没有办法可以使用清单来使其正常工作? 谢谢!
不幸的是,这是asInstanceOf
的固有限制。 看到 scaladoc详细提到它,我真的很惊讶:
请注意,运行时强制转换的成功是模 Scala 的擦除语义。 因此,表达式
1.asInstanceOf[String]
将在运行时抛出ClassCastException
,而表达式List(1).asInstanceOf[List[String]]
不会。 在后一个示例中,因为类型参数作为编译的一部分被擦除,所以无法检查列表的内容是否属于请求的类型。
如果您主要担心在可遍历的错误转换上快速失败,这可能是从 DB/memcached 接口取回内容时的主要问题,我正在玩强制转换可遍历对象的头部:
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
}
}
在一个简单的例子中,它可以工作:
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)
但不是更复杂的:
val x = Map(1 -> ("s" -> 1L)): Any
failFastCast(Map[Int, (String, String)](), x) // no throw
我想知道是否有一种方法可以递归深入到 A 以继续强制转换,直到没有更多类型参数...
您确实是正确的-例如,类型擦除意味着您不能以区分List[Int]
和List[String]
的方式“投射”。 但是,您可以改进您正在执行的演员表,其中A
被删除,这意味着您无法区分Int
和String
:
def cast[A](a : Any) = a.asInstanceOf[A]
//... is erased to
def erasedCast(a : Any) = a.asInstanceOf[Any]
你需要的是reified generics ,使用清单
def cast[A <: AnyRef : Manifest](a : Any) : A
= manifest[A].erasure.cast(a).asInstanceOf[A]
虽然最终的演员表被擦除为AnyRef
,但至少您应该拥有正确的Class[_]
实例( manifest.erasure
)来获得正确的顶级类型。 在行动:
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)
我的建议是不要使用嵌套反射来确定目标是否真的是List[Int]
:这通常是不可行的。 以下应该返回什么?
cast[List[Int]](List[AnyVal](1, 2))
您可以使用 Miles Sabin 的shapeless的 Typeable:
它在许多情况下处理擦除,尽管只有特定的:
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
要查看它处理的案例集,您可以参考其来源:
https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala
考虑这个解决方案:
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)
优点是:
现在你可以这样使用它:
scala> cast(new java.util.Date())
res9: Long = 1361195427192
scala> cast("123")
res10: Int = 123
编辑
我花了一些时间编写了这个高级代码。 首先让我展示如何使用它:
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]
^
挺棒的? 请参阅 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)
}
是的,问题是由于类型擦除而发生的。 如果你试试
val x = List(1,2,3)
val y = castToType[Int](x)
正如预期的那样,立即抛出异常。 尝试转换为Array[String]
甚至Array[Int]
时也会发生同样的情况。
我不认为你可以创建一个通用类型转换器,它可以在 collections 和其他对象中输入类型。 您需要为每个 object 类型创建一个转换器。 例如:
def castToType[A](x: List[A]) = x.map(i => i.asInstanceOf[A])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.