带有参数化类型的 Scala asInstanceOf
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6686992/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scala asInstanceOf with parameterized types
提问by Pandora Lee
I would like to write a function that casts to type A, where A can be e.g. List[Int], or a more complicated parameterized type like Map[Int, List[Int]].
我想编写一个转换为类型 A 的函数,其中 A 可以是例如 List[Int],或者更复杂的参数化类型,如 Map[Int, List[Int]]。
def castToType[A](x: Any): A = {
// throws if A is not the right type
x.asInstanceOf[A]
}
Right now, due to type erasure (I believe), the code merrily works even when the type is not correct. The error only manifests on access, witha ClassCastException.
现在,由于类型擦除(我相信),即使类型不正确,代码也能正常工作。该错误仅在访问时出现,带有 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
Is there a way I can use manifests to make this work properly? Thanks!
有没有办法可以使用清单来使其正常工作?谢谢!
采纳答案by huynhjl
Unfortunately, this in an inherent limitation of asInstanceOf. I'm actually surprised to see the scaladoc mention it in details:
不幸的是,这在asInstanceOf. 看到 scaladoc详细提到它,我真的很惊讶:
Note that the success of a cast at runtime is modulo Scala's erasure semantics. Therefore the expression
1.asInstanceOf[String]will throw aClassCastExceptionat runtime, while the expressionList(1).asInstanceOf[List[String]]will not. In the latter example, because the type argument is erased as part of compilation it is not possible to check whether the contents of the list are of the requested type.
请注意,运行时强制转换的成功是模 Scala 的擦除语义。因此表达式
1.asInstanceOf[String]会ClassCastException在运行时抛出 a ,而表达式List(1).asInstanceOf[List[String]]不会。在后一个例子中,因为类型参数作为编译的一部分被擦除,所以不可能检查列表的内容是否是请求的类型。
If you're mainly concerned about failing fast on wrong cast for traversable which would likely be the main issue when getting stuff back from your DB/memcached interface, I was playing around forcing a cast of the head for traversable objects:
如果您主要担心在错误的可遍历转换上快速失败,这可能是从 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
}
}
On a simple example it works:
在一个简单的例子中它的工作原理:
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)
But noton a more complex one:
但不是更复杂的:
val x = Map(1 -> ("s" -> 1L)): Any
failFastCast(Map[Int, (String, String)](), x) // no throw
I wonder if there is a way to recursively drill down into A to keep casting until there is no more type parameters...
我想知道是否有一种方法可以递归地向下钻取 A 以继续转换,直到没有更多类型参数...
回答by oxbow_lakes
You are indeed correct - type erasure means that you cannot "cast" in such a way as to distinguish between List[Int]and List[String], for example. However, you can improve on the castwhich you are performing, whereby Ais erased in such a way as to mean that you cannot distinguish between an Intand a String:
您确实是正确的 - 例如,类型擦除意味着您不能以区分List[Int]和的方式“投射” List[String]。但是,您可以改进您正在执行的演员表,从而A以这样的方式擦除,这意味着您无法区分 anInt和 a String:
def cast[A](a : Any) = a.asInstanceOf[A]
//... is erased to
def erasedCast(a : Any) = a.asInstanceOf[Any]
What you need are reified generics, using manifests
你需要的是具体化泛型,使用清单
def cast[A <: AnyRef : Manifest](a : Any) : A
= manifest[A].erasure.cast(a).asInstanceOf[A]
Whilst the final cast is erased to AnyRef, at least you should have the correct Class[_]instance (manifest.erasure) to get the top level type correct. In action:
虽然最后的强制转换被擦除为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)
My advice is not to use nested reflection to decide whether the target was really a List[Int]: this is not generally feasible. For what should the following return?
我的建议是不要使用嵌套反射来确定目标是否真的是 a List[Int]:这通常不可行。以下应该返回什么?
cast[List[Int]](List[AnyVal](1, 2))
回答by tksfz
You could use shapeless's Typeable from Miles Sabin:
你可以使用无形的“从万里萨宾Š分型:
Type casting using type parameter
It handles erasure in many cases, though only specific ones:
它在许多情况下处理擦除,尽管只有特定的:
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
To see the set of cases that it handles you can refer to its source:
要查看它处理的案例集,您可以参考其来源:
https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala
https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala
回答by pawel.panasewicz
Consider this solution:
考虑这个解决方案:
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)
The advantage is that:
优点是:
- It is type safe - the compiler will tell you if the type cannot be casted
- You can define casting rules by providing proper implicits
- 它是类型安全的——编译器会告诉你该类型是否不能被强制转换
- 您可以通过提供适当的隐式来定义转换规则
Now you can use it so:
现在你可以这样使用它:
scala> cast(new java.util.Date())
res9: Long = 1361195427192
scala> cast("123")
res10: Int = 123
EDIT
编辑
I've spent some time and wrote this advanced code. First let me show how to use it:
我花了一些时间写了这个高级代码。首先让我展示如何使用它:
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]
^
Pretty nice? See the scala magic:
挺好的?看到 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)
}
回答by rafalotufo
Yes, the problem occurs due to type erasure. If you try
是的,问题是由于类型擦除而发生的。如果你试试
val x = List(1,2,3)
val y = castToType[Int](x)
The exception is thrown right away, as expected. The same occurs when trying to cast to Array[String]or even Array[Int].
正如预期的那样,异常立即被抛出。尝试强制转换为Array[String]甚至时也会发生同样的情况Array[Int]。
I don't think you can create a generic type converter that works will types inside collections and other objects. You will need to create a converter for each object type. For example:
我不认为您可以创建一个通用类型转换器,该转换器可以在集合和其他对象中使用类型。您需要为每种对象类型创建一个转换器。例如:
def castToType[A](x: List[A]) = x.map(i => i.asInstanceOf[A])

