【问题标题】:Scala asInstanceOf with parameterized types具有参数化类型的 Scala asInstanceOf
【发布时间】:2011-07-13 23:47:16
【问题描述】:

我想编写一个转换为类型 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

有没有办法可以使用清单来使其正常工作?谢谢!

【问题讨论】:

  • 您能否提供更多关于为什么需要投射的背景信息?在您的示例val x = List(1, 2, 3) 中,编译器推断List[Int],因此使用castToType 似乎毫无意义。如果您可以完全避免AnyasInstanceOf,那是最好的选择。
  • 是的,基本上我的 DB/memcached 接口返回一个 Any,而且我有一个需要返回类型 R 的类型化类(参见 stackoverflow.com/questions/5985076/…)。所以我需要对让编译器高兴,但如果演员表不正确,我也希望它也能抛出。

标签: scala


【解决方案1】:

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

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

如果您主要担心在错误的 traversable 转换时快速失败,这可能是从 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 以继续强制转换,直到没有更多类型参数...

【讨论】:

    【解决方案2】:

    您确实是正确的-类型擦除意味着您不能以区分List[Int]List[String] 的方式“投射”,例如。但是,您可以改进您正在执行的演员表,其中A 被删除,这意味着您无法区分IntString

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

    【讨论】:

      【解决方案3】:

      您可以使用 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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 您的castToType 方法不只是一个花哨的身份功能吗?您的意思是参数类型为List[Any] 吗?有趣的是,即使您将其设为List[Any],它仍然不会抛出。
        【解决方案5】:

        考虑这个解决方案:

        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. 您可以通过提供适当的隐式来定义强制转换规则

        现在你可以这样使用它了:

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

        【讨论】:

        • 如果你允许一点点吹毛求疵,那么你就是在展示一个转换,而不是一个演员表。确实,通过类型系统表达转换的能力是 Scala(和 Haskell)最漂亮的特性之一,但正是类型擦除问题阻碍了在实践中的许多情况:如果您丢失了详细的类型信息并且只留下了AnyVal,那么您也失去了根据详细类型选择转换的能力
        猜你喜欢
        • 1970-01-01
        • 2018-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多