Scala 谜题 - 有趣的类型转换

在 Scala 中,List[String] 和 List[Int] 之间并没有继承关系,但是下面的代码竟然可以通过编译并且顺利运行:

object Test extends App {
  val strList: List[String] = List("a", "b", "c")
  val strToIntList: List[Int] = strList.asInstanceOf[List[Int]]
  println(strToIntList)
}

输出:

//输出: List(a, b, c)

是的,你没看错!我们把 List[String] 成功的转换成了 List[Int] 类型。事实上真的是这样吗? 让我们来测试一下:

object Test extends App {
  val strList: List[String] = List("a", "b", "c")
  val strToIntList: List[Int] = strList.asInstanceOf[List[Int]]
  val head = strToIntList(0)
  println(head)
}

输出:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
    ...
	at test.Test.main(Test.scala)

哈哈,抛出了类型转换异常。编译器推断出 head 类型为 Int 型,但在运行时却被赋予了 String 型,所以导致了运行时错误。

翻看 Scala 文档终于找到了原因:

final def asInstanceOf[T0]: T0
Cast the receiver object to be of type T0.
Note that the success of a cast at runtime is modulo Scala's erasure semantics. Therefore the expression 1.asInstanceOf[String]will throw a ClassCastException at runtime, while the expression List(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.

在调用 asInstanceOf 方法时,编译器给予开发者足够的信任,认为你有足够的理由去这样做。但是在运行时,由于泛型类的类型参数被擦除了,所以 List[String] 和 List[Int] 在运行时都是 List 类型,但是在操作其元素时要格外小心,否则会抛出类型转换异常。

利用这个特性我们可以写出一些很有意思的代码,虽然 Class[T] 是 invariant 的,利用 asInstanceOf 方法可以让它变成 covariant,示例代码如下:

object Test extends App {
  val jsObjClass:   Class[JsObject] = classOf[JsObject]
  val jsValueClass: Class[JsValue]  = jsObjClass.asInstanceOf[Class[JsValue]]
}

由于在运行时 JsObject 可以被成功地转换为 JsValue,所以上述代码可以正常工作。

转载请注明来自 PlayScala社区 。

猜你喜欢

转载自my.oschina.net/joymufeng/blog/1632958