这当然很奇怪。
如果您查看 Scala REPL 中的以下语句:
scala> val x = m.getValue[Int]("2")
x: Option[Int] = Some(two)
我认为正在发生的事情是这样的:asInstanceOf[T] 语句只是向编译器标记结果应该是Int,但不需要强制转换,因为该对象仍然只是通过指针引用。 (并且Int 值被装箱在Option/Some 内).toString 有效,因为每个对象都有一个.toString 方法,它只对值“二”进行操作以产生“二”。但是,当您尝试将结果分配给 Int 变量时,编译器会尝试将存储的整数拆箱,结果是一个强制转换异常,因为该值是 String 而不是装箱的 Int。
让我们在 REPL 中逐步验证这一点:
$ scala
Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151).
Type in expressions for evaluation. Or try :help.
scala> class MapOps(map: Map[String, Any]) {
| def getValue[T](name: String): Option[T] = {
| map.get(name).map{_.asInstanceOf[T]}
| }
| }
defined class MapOps
scala> import scala.language.implicitConversions
import scala.language.implicitConversions
scala> implicit def toMapOps(map: Map[String, Any]): MapOps = new MapOps(map)
toMapOps: (map: Map[String,Any])MapOps
scala> val a = m.getValue[Int]("2").get.toString
a: String = two
scala> println(s"1: $a")
1: two
到目前为止一切顺利。请注意,到目前为止还没有抛出异常,即使我们已经使用了.asInstanceOf[T] 并在结果值上使用了get。重要的是我们没有尝试对get 调用的结果(名义上是一个装箱的Int,实际上是String 值“二”)做任何事情,除了调用它的toString 方法。这行得通,因为String 值具有toString 方法。
现在让我们对Int 变量进行赋值:
scala> val b = m.getValue[Int]("2").get
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
... 29 elided
现在我们得到了异常!还要注意堆栈跟踪中导致它的函数:unboxToInt - 它显然试图将存储在Some 中的值转换为Int,但它失败了,因为它不是装箱的Int,而是String.
问题的很大一部分是类型擦除。不要忘记 Some(Banana) 和 Some(Bicycle) 在运行时都只是带有指向某个对象的指针的 Some 实例。 .asInstanceOf[T] 无法验证类型,因为该信息已被删除。但是,编译器能够根据您告诉它的内容来跟踪类型应该是什么,但它只能在其假设被证明是错误的情况下检测到错误。
最后,关于getClass 调用的结果。这有点像编译器的花招。它实际上并没有在对象上调用 getClass 函数,而是 - 因为它认为它正在处理一个原始的 Int - 它只是替换了一个 int 类实例。
scala> m.getValue[Int]("2").get.getClass
res0: Class[Int] = int
要验证对象实际上是String,您可以将其转换为Any,如下所示:
scala> m.getValue[Int]("2").get.asInstanceOf[Any].getClass
res1: Class[_] = class java.lang.String
关于get的返回值的进一步验证如下;请注意,当我们将此方法的结果分配给Any 类型的变量时没有异常(因此不需要强制转换),事实上,带有键“1”的有效Int 实际上存储在Any 下作为一个装箱的Int (java.lang.Integer),并且后一个值可以成功地拆箱为常规的Int 原语:
scala> val x: Any = m.getValue[Int]("2").get
x: Any = two
scala> x.getClass
res2: Class[_] = class java.lang.String
scala> val y: Any = m.getValue[Int]("1").get
y: Any = 1
scala> y.getClass
res3: Class[_] = class java.lang.Integer
scala> val z = m.getValue[Int]("1").get
z: Int = 1
scala> z.getClass
res4: Class[Int] = int