【发布时间】:2011-03-25 15:12:35
【问题描述】:
我看到Scala中有两种方法可以转换对象:
foo.asInstanceOf[Bar]
(foo: Bar)
当我尝试时,我发现asInstanceOf 不使用隐式转换,而另一个使用。
这两种方法的行为有何不同?建议在哪里使用一个而不是另一个?
【问题讨论】:
我看到Scala中有两种方法可以转换对象:
foo.asInstanceOf[Bar]
(foo: Bar)
当我尝试时,我发现asInstanceOf 不使用隐式转换,而另一个使用。
这两种方法的行为有何不同?建议在哪里使用一个而不是另一个?
【问题讨论】:
Scala 编程在Chapter 15 - Case Classes and Pattern Matching 中详细介绍了这一点。
基本上第二种形式可以在模式匹配中用作Typed Pattern,提供isInstanceOf 和asInstanceOf 功能。比较
if (x.isInstanceOf[String]) {
val s = x.asInstanceOf[String]
s.length
} else ...
对比
def checkFoo(x: Any) = x match {
case s: String => s.length
case m: Int => m
case _ => 0
}
作者暗示isInstance* 处理方式的冗长是有意将您推向模式匹配风格。
我不确定哪种模式对于没有测试的简单类型转换更有效。
【讨论】:
foo.asInstanceOf[Bar] 是 cast 类型,主要是运行时操作。它说应该强制编译器相信foo 是Bar。如果foo 在运行时被评估为不是Bar,这可能会导致错误(ClassCastException)。
foo:Bar 是一个类型ascription,完全是编译时操作。这是在帮助编译器理解代码的含义,而不是强迫它相信任何可能不真实的东西;使用类型属性不会导致运行时失败。
类型归属也可用于触发隐式转换。例如,您可以定义以下隐式转换:
implicit def foo(s:String):Int = s.length
然后确保它像这样使用:
scala> "hi":Int
res29: Int = 2
将类型Int 赋予String 通常是编译时类型错误,但在放弃之前,编译器会搜索可用的隐式转换以使问题消失。将在给定上下文中使用的特定隐式转换在编译时是已知的。
不用说,运行时错误是不可取的,因此您可以在多大程度上以类型安全的方式指定事物(不使用asInstanceof),那就更好了!如果您发现自己使用的是asInstanceOf,那么您可能应该使用match。
【讨论】:
foo: Bar 被称为类型归属。使用这个术语会很好,因为它可以更容易地找到有关它的更多信息。或在搜索时找到此答案。 :-)
Pelotom 的回答很好地涵盖了理论,这里有一些例子可以更清楚地说明:
def foo(x: Any) {
println("any")
}
def foo(x: String) {
println("string")
}
def main(args: Array[String]) {
val a: Any = new Object
val s = "string"
foo(a) // any
foo(s) // string
foo(s: Any) // any
foo(a.asInstanceOf[String]) // compiles, but ClassCastException during runtime
foo(a: String) // does not compile, type mismatch
}
如您所见,类型归属可用于解决歧义。有时它们可能无法被编译器解析(见下文),编译器会报告错误,您必须解决它。在其他情况下(如示例中),它只是使用“错误”的方法,而不是您想要的。 foo(a: String) 不编译,表明类型归属不是强制转换。将其与上一行进行比较,编译器很高兴,但您得到了一个异常,因此使用类型属性检测到错误。
如果你还添加一个方法,你会得到一个无法解决的歧义
def foo(xs: Any*) {
println("vararg")
}
在这种情况下,第一次和第三次调用 foo 将无法编译,因为编译器无法决定是使用单个 Any 参数还是使用可变参数调用 foo,因为它们似乎都一样好=> 你必须使用类型归属来帮助编译器。
【讨论】:
有区别的例子:
例子:
class Parent() { def method() {} }
class Child1 extends Parent() { def method1() {} }
class Child2 extends Parent() { def method2() {} }
// we return Parent type
def getChild1() : Parent = new Child1()
def getChild2() : Parent = new Child2()
def getChild() : Child1 = new Child1()
(getChild1().asInstanceOf[Child1]).method1() // OK
(getChild1().asInstanceOf[Child2]).method2() // runtime ClassCastException
(getChild1() : Child2).method2() // compile-time error
(getChild2() : Child2).method2() // compile-time error
(getChild() : Parent).method1() // compile-time error
(getChild()).method() // OK
// with asInstanceOf, we can cast to anything without compile-time error
getChild1().asInstanceOf[String] // runtime ClassCastException
getChild1().asInstanceOf[Int] // runtime ClassCastException
我们也可以使用multiple-dispatch来调用方法:
def prt(p: Parent) = println("parent")
def prt(ch: Child1) = println("child")
prt(new Parent()) // prints "parent"
prt((new Child1()) : Parent) // prints "parent"
prt(new Child1()) // prints "child"
prt(new Parent().asInstanceOf[Child1]) // runtime ClassCastException
prt(new Child1().asInstanceOf[Parent]) // prints "parent"
我们可以定义隐式转换:
// after definition of implicit conversions
implicit def toChild1(p: Parent) : Child1 = new Child1()
implicit def toChild2(p: Parent) : Child2 = new Child2()
(getChild1() : Child2).method2() // OK - implicit conversion to Child2 in ascription
(getChild2() : Child2).method2() // OK - implicit conversion to Child2 in ascription
(getChild2()).method1() // OK - implicit conversion to Child1 when calling method1()
(getChild2()).method2() // OK - implicit conversion to Child2 when calling method2()
(getChild2() : Parent).method() // OK - no implicit conversion
(getChild() : Parent).method1() // OK - implicit conversion to Child1 when calling method()
getChild1().asInstanceOf[Int] // still runtime ClassCastException (no implicit conversion)
【讨论】: