Scala 为什么用Option包装泛型方法调用会延迟ClassCastException?
假设我有一个这样的数组*:Scala 为什么用Option包装泛型方法调用会延迟ClassCastException?,scala,generics,classcastexception,scala-option,scala-generics,Scala,Generics,Classcastexception,Scala Option,Scala Generics,假设我有一个这样的数组*: 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 : In
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
但是当使用选项包装时
成功:
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中测试
***如果您对上下文感兴趣,可以查看
****评论中链接的其他相关问题:
- ,
任何
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])
因为您使用泛型参数创建了一个方法,所以运行时不需要“接触”该值,因为它不在乎。泛型将在运行时被视为
任何
/对象。查看以下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
}
正如您所看到的,checkcast
是在getAs
之后执行的,而不是在期间执行的,并且只在非通用上下文中执行。这让我之前就感到困惑。看看这个:这和你的问题不完全一样,但答案也适用于你的情况。好吧,这让人困惑。我需要一些时间来考虑一下。感谢您指出@DIMA可能与之相关(虽然它在列表中,但可能适用于所有复杂类型F[\u]
)。@ChrisMartin这始终是一个很好的建议,但编译器似乎对此非常满意。你有什么特别的选择吗?@zero323我错了,抱歉没有测试就评论。我原以为由于部分擦除,.asInstanceOf[T]
是一个未经检查的强制转换,但再看一眼,我发现它不是。
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;
}