带参数化类型的Scala asInstanceOf

时间:2011-07-13 23:47:17

标签: scala

我想写一个转换为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]
}

现在,由于类型擦除(我相信),即使类型不正确,代码也会快速工作。该错误仅在访问时显示,具有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

有没有办法可以使用清单来使这项工作正常进行?谢谢!

5 个答案:

答案 0 :(得分:18)

不幸的是,这是asInstanceOf的固有限制。我真的很惊讶scaladoc在details中提到它:

  

请注意,在运行时强制转换的成功是以Scala的擦除语义为模。因此,表达式1.asInstanceOf[String]将在运行时抛出ClassCastException,而表达式List(1).asInstanceOf[List[String]]则不会。在后一个示例中,因为类型参数作为编译的一部分被删除,所以无法检查列表的内容是否是所请求的类型。

如果你主要担心在可穿越的错误演员表上快速失败,这可能是从你的DB / memcached接口返回东西时的主要问题,那我就是在为可遍历的对象强制转换头部:< / p>

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以继续投射,直到没有更多的类型参数...

答案 1 :(得分:12)

你确实是正确的 - 例如,类型擦除意味着你不能以“List[Int]List[String]之间的方式”施放“。但是,您可以改善正在执行的广告,因此A会被删除,这意味着您无法区分Int和{{{ 1}}:

String

您需要的是使用清单

reified generics
def cast[A](a : Any) = a.asInstanceOf[A]
//... is erased to
def erasedCast(a : Any) = a.asInstanceOf[Any]

虽然最终演员阵容被删除为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]

答案 2 :(得分:5)

你可以使用来自Miles Sabin的shapeless的Typeable:

Type casting using type parameter

它在许多情况下处理擦除,但只是特定的擦除:

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

答案 3 :(得分:2)

是的,问题是由于类型擦除而发生的。如果你试试

val x = List(1,2,3)
val y = castToType[Int](x)

正如预期的那样,会立即抛出异常。尝试转换为Array[String]甚至Array[Int]时会出现同样的情况。

我认为你不能创建一个泛型类型转换器,它可以在集合和其他对象中使用类型。您需要为每种对象类型创建一个转换器。例如:

def castToType[A](x: List[A]) = x.map(i => i.asInstanceOf[A])

答案 4 :(得分:2)

考虑这个解决方案:

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)

优点是:

  1. 类型安全 - 编译器会告诉您类型是否无法投放
  2. 您可以通过提供适当的含义来定义投射规则
  3. 现在您可以使用它:

    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 magic:

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