Почему завершение общего вызова метода с опцией отменяет ClassCastException?

Давайте скажем, что у меня есть такой массив:

val foo: Any = 1 : Int
Option(foo.asInstanceOf[String])

который невозможен по понятной причине:

// java.lang.ClassCastException: java.lang.Integer cannot be cast to 
// java.lang.String
// ... 48 elided

Далее рассмотрим следующий класс:

case class DummyRow() {
  val foo: Any = 1 : Int
  def getAs[T] = foo.asInstanceOf[T]
  def getAsOption[T] = Option(foo.asInstanceOf[T])
}

Насколько я могу судить, getAs должен вести себя так же, как предыдущий apply, за которым следует asInstanceOf.

Удивительно, но это не так. При вызове в одиночку генерируется исключение:

DummyRow().getAs[String]
// java.lang.ClassCastException: java.lang.Integer cannot be cast to 
// java.lang.String
// ... 48 elided

но при завершении Option выполняется:

val stringOption = Option(DummyRow().getAs[String])
// Option[String] = Some(1)

DummyRow().getAsOption[String]
// Option[String] = Some(1)

и не удается только при попытке получить доступ к завернутому значению:

stringOption.get
// java.lang.ClassCastException: java.lang.Integer cannot be cast to 
// java.lang.String
// ... 48 elided

И что здесь происходит? Кажется, он ограничен ClassCastException, поэтому я думаю, что это связано с некоторой уродливой стилей, типа стирания стилей.


* Any и asInstanceOf существуют, чтобы имитировать поведение стороннего кода, поэтому, пожалуйста, не останавливайтесь на этом.

** Протестировано в Scala 2.10.5, 2.11.7

*** Если вас интересует контекст, вы можете взглянуть на Использование содержит в Scala - исключение

**** Другие актуальные вопросы, связанные с комментариями:

Ответы

Ответ 1

Ниже приведен упрощенный вариант вашей проблемы с дополнительным случаем для Any

def getAs[T] = (1:Int).asInstanceOf[T]

//blows up
getAs[String]

//blows up
def p(s:String): Unit = {}
p(getAs[String])

//works
def p[T](s:T): Unit = {}
p(getAs[String])

//works
def p(s:Any): Unit = {}
p(getAs[String])

Поскольку вы создаете метод с общим параметром, среда выполнения не должна "касаться" значения, потому что это не волнует. Во время выполнения Generic будет обрабатываться как Any/Object.

Ответ 2

Взгляните на следующий (слегка отредактированный для чтения) сеанс REPL:

scala> class Foo(foo: Any) {
     | def getAs[T] = foo.asInstanceOf[T]
     | def getAsString = foo.asInstanceOf[String]
     | }
defined class Foo

scala> :javap Foo
  Size 815 bytes
  MD5 checksum 6d77ff638c5719ca1cf996be4dbead62
  Compiled from "<console>"
public class Foo
{
  public <T extends java/lang/Object> T getAs();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #11                 // Field foo:Ljava/lang/Object;
         4: areturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   LFoo;
      LineNumberTable:
        line 12: 0
    Signature: #35                          // <T:Ljava/lang/Object;>()TT;

  public java.lang.String getAsString();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #11                 // Field foo:Ljava/lang/Object;
         4: checkcast     #17                 // class java/lang/String
         7: areturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       8     0  this   LFoo;
      LineNumberTable:
        line 13: 0
}

В байт-коде getAsString вы можете видеть, что при выполнении функции String выполняется команда checkcast. С другой стороны, в getAs[T] такая команда не выполняется, даже если в коде есть бросок. Причиной этого является то, что T стирается до Any во время выполнения, поэтому он просто станет отличным от Any (который никогда не будет терпеть неудачу). Поэтому приведение к параметру типа необходимо только для компилятора, а не для JVM. Таким образом, кастинг не должен происходить, когда вы завершаете этот вызов в Option, который также является общим. Это только тогда, когда вы хотите получить значение из Option и рассматривать его как String, что выполняется бросок и генерируется исключение.

scala> class Bar() {
     | def getString: String = new Foo(3).getAs[String]
     | def get[T]: T = new Foo(3).getAs[T]
     | }
defined class Bar

scala> :javap Bar
  Size 1005 bytes
  MD5 checksum 4b7bee878db4235ca9c011c6f168b4c9
  Compiled from "<console>"
public class Bar
{
  public java.lang.String getString();
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #9                  // class Foo
         3: dup           
         4: iconst_3      
         5: invokestatic  #15                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
         8: invokespecial #19                 // Method Foo."<init>":(Ljava/lang/Object;)V
        11: invokevirtual #23                 // Method Foo.getAs:()Ljava/lang/Object;
        14: checkcast     #25                 // class java/lang/String
        17: areturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      18     0  this   LBar;
      LineNumberTable:
        line 13: 0

  public <T extends java/lang/Object> T get();
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #9                  // class Foo
         3: dup           
         4: iconst_3      
         5: invokestatic  #15                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
         8: invokespecial #19                 // Method Foo."<init>":(Ljava/lang/Object;)V
        11: invokevirtual #23                 // Method Foo.getAs:()Ljava/lang/Object;
        14: areturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      15     0  this   LBar;
      LineNumberTable:
        line 14: 0
    Signature: #51                          // <T:Ljava/lang/Object;>()TT;
}

Как вы можете видеть, checkcast выполняется после getAs, а не во время и только в не-общий контекст.