以下代码:
object Test {
class MapOps(map: Map[String, Any]) {
def getValue[T](name: String): Option[T] = {
map.get(name).map{_.asInstanceOf[T]}
}
}
implicit def toMapOps(map: Map[String, Any]): MapOps = new MapOps(map)
def main(args: Array[String]): Unit = {
val m: Map[String, Any] = Map("1" -> 1, "2" -> "two")
val a = m.getValue[Int]("2").get.toString
println(s"1: $a")
val b = m.getValue[Int]("2").get
println(s"2: $b")
}
}
计算 val a
无例外,控制台打印1: two
,
但是在计算val b
时,会抛出java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
。
此外,如果我执行
val c = m.getValue[Int]("2").get.getClass.toString
println(s"1: $c")
控制台打印“int”。
有人可以解释为什么这段代码会像这样吗?
答案 0 :(得分:6)
这当然很奇怪。
如果您在 Scala REPL 中查看以下语句:
scala> val x = m.getValue[Int]("2")
x: Option[Int] = Some(two)
我认为发生的是:asInstanceOf[T]
语句只是向编译器标记结果应该是Int
,但是不需要强制转换,因为对象仍然只是通过一个指针。 (Int
/ Option
} Some
内部.toString
值被加框,因为每个对象都有一个.toString
方法,只对值“两个“屈服”两个“。但是,当您尝试将结果分配给Int
变量时,编译器会尝试取消存储整数,并且结果是强制转换异常,因为该值为String
而不是装箱{ {1}}。
让我们在 REPL 中逐步验证:
Int
到目前为止一切顺利。请注意,到目前为止,没有抛出任何异常,即使我们已使用$ 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
,但它失败了,因为它不是盒装{{ 1}}但是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
的变量时没有异常(因此不需要强制转换),实际存储了带有键“1”的有效get
的事实在Any
下作为框Int
(Any
),后一个值可以成功解包到常规Int
原语:
java.lang.Integer
答案 1 :(得分:0)
扩展eje211's answer的最后一部分。
您告诉编译器String
是Int
,现在您正在寻找对结果行为的合理解释。这是可以理解的,但它并没有真正有用。一旦你告诉编译器一个谎言,所有的赌注都会关闭。您可以花时间仔细研究编译器何时何地插入发现您的欺骗行为的检查,但是编写不会导致您撒谎的代码可能会花费更多时间。
正如前面的回答所指出的,你可以通过使用模式匹配来做到这一点(避免意外说谎)。在上述情况下,您需要ClassTag
进行模式匹配工作,但最终结果将是类型安全且正确的代码。
答案 2 :(得分:-1)
它是Int
,因为您在此行请求了Int
:
val b = m.getValue[Int]("2").get
这称为此方法:
def getValue[T](name: String): Option[T] = {
map.get(name).map{_.asInstanceOf[T]}
}
并以这种方式应用:
def getValue[Int](name: String): Option[Int] = {
map.get(name).map{_.asInstanceOf[Int]}
}
因此,如果您要求Int
,则会获得Int
。
在"two"
的情况下,在这种情况下会发生这种情况:
"two".asInstanceOf[Int]
是什么引发了你的异常。
String
不是Int。你不能这样投。你可以这个方式投射它:
"2".toInt
但那是不同的。
通常,使用asInstanceOf[]
是危险的。请尝试模式匹配。如果您必须使用,那么您可以确保您尝试的演员表有效。您基本上是告诉编译器绕过自己的类型检查,尤其是当您从Any
进行转换时。
当你添加.toString
时,它会起作用,因为那时你将类型改回String,这就是它最初的真实情况。更正了数据类型的谎言。