Почему завершение общего вызова метода с опцией отменяет 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
, а не во время и только в не-общий контекст.