【问题标题】:Why does `.asInstanceOf` sometimes throw, and sometimes not?为什么 .asInstanceOf 有时会抛出,有时不会?
【发布时间】:2015-12-11 03:06:59
【问题描述】:

我试图回答this 的问题,因为我以为我知道答案。 原来,我还不够了解:/

这是我做过的一个测试:

class Inst[T] { 
  def is(x: Any) = scala.util.Try { as(x) }.isSuccess
  def as(x: Any): T = x.asInstanceOf[T]
}

scala> new Inst[String].is(3)
res17: Boolean = true

scala> new Inst[String].as(3)
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
 ... 33 elided

这里发生了什么?为什么只有第二次调用as 抛出,而不是第一次?

【问题讨论】:

    标签: scala


    【解决方案1】:

    这是因为 class-cast-exception 仅在您使用该值执行某些操作时抛出,在转换后调用它的方法。例如,在 REPL 中,您将在第二种情况下调用 toString。注意:

    new Inst[String].as(3); ()           // nothing happens
    new Inst[String].as(3).toString; ()  // exception
    

    之所以采取额外步骤是因为Inst[T] 是泛型的,类型参数T 在运行时被擦除;只有当调用站点(具有T 类型的静态知识)尝试调用结果上的方法时,才会发生实际的类型检查。


    对于您的后续问题,toString 是在任何对象上定义的,并且由于 T 是通用的,因此您有一个装箱整数 (<: AnyRef) 和 toStringprintlnis 方法中成功.所以Try 会失败的另一个例子是:

    class Inst[T] { 
      def foo(x: Any)(implicit ev: T <:< String) = scala.util.Try {
        ev(as(x)).reverse
      }.isSuccess
    }
    
    new Inst[String].foo(3)  // false!
    

    【讨论】:

    • 不,这似乎不能解释太多:我将is 定义更改为:def is(x: Any) = scala.util.Try { as(x).toString }.isSuccess,它仍然返回true(即演员不会抛出)。甚至这个def is(x: Any) = scala.util.Try { println(as(x).toString) }.isSuccess; 也很高兴地打印出“3”并返回true:-/
    • 啊,现在说得通了,谢谢! is 不知道T 是什么,所以它将参数视为Any。我试过这个:trait Foo { def foo = ??? } class Inst[T &lt;: Foo] { def is(x: Any) = scala.util.Try { as(x).foo }.isSuccess; def as(x: Any): T = x.asInstanceOf[T]; }。现在new Inst[Foo].is(3) 按预期返回false
    • 是的,toString 不是一个很好的例子(我之所以使用它是因为那是在 REPL 中为as 发生的事情),因为在Inst 中你已经删除了T 类型到 java.lang.Object 又名 scala.AnyRef 定义了 toString,因此那里也不例外。
    【解决方案2】:

    虽然@0__ 的回答解释了它为什么不起作用,但下面是如何使它起作用:

    class Inst[T](implicit tag: scala.reflect.ClassTag[T]) {
      def is(x: Any) = tag.runtimeClass.isInstance(x) 
      // scala.util.Try { as(x) }.isSuccess will work as well
      def as(x: Any): T = tag.runtimeClass.cast(x).asInstanceOf[T]
    }
    
    object Main extends App {
      println(new Inst[String].is(3))
      println(new Inst[String].as(3))
    }
    
    
    false
    java.lang.ClassCastException: Cannot cast java.lang.Integer to java.lang.String
        at java.lang.Class.cast(Class.java:3369)
    ...
    

    【讨论】: