Каковы различия между asInstanceOf[T] и (o: T) в Scala?
Я видел, что есть два способа приведения объекта в Scala:
foo.asInstanceOf[Bar]
(foo: Bar)
Когда я попытался, я обнаружил, что asInstanceOf
не использует неявное преобразование, в то время как другой использует.
Каковы различия в поведении между этими двумя методами? И где рекомендуется использовать один поверх другого?
4 ответа
foo.asInstanceOf[Bar]
это приведение типа, которое в первую очередь является операцией во время выполнения. Это говорит о том, что компилятор должен быть вынужден поверить, чтоfoo
этоBar
, Это может привести к ошибке (ClassCastException
) если и когдаfoo
оценивается как нечто иное, чемBar
во время выполнения.foo:Bar
является типовойнадписью, которая полностью выполняется во время компиляции. Это помогает компилятору понять смысл вашего кода, не заставляя его верить всему, что может быть неверным; никакие сбои во время выполнения не могут возникнуть в результате использования типовых атрибутов.
Атрибуты типа также могут использоваться для запуска неявных преобразований. Например, вы можете определить следующее неявное преобразование:
implicit def foo(s:String):Int = s.length
а затем обеспечить его использование следующим образом:
scala> "hi":Int
res29: Int = 2
Приписывая типInt
кString
обычно это ошибка типа времени компиляции, но перед отказом компилятор будет искать доступные неявные преобразования, чтобы устранить проблему. Конкретное неявное преобразование, которое будет использоваться в данном контексте, известно во время компиляции.
Излишне говорить, что ошибки времени выполнения нежелательны, поэтому степень, в которой вы можете указывать вещибезопасным для типов образом (без использования asInstanceof
), лучшее! Если вы обнаружите, что используете asInstanceOf
, вы, вероятно, должны использовать match
вместо.
Ответ Пелотома охватывает теорию довольно хорошо, вот несколько примеров, чтобы прояснить ее:
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
}
Как вы можете видеть, тип ascription может быть использован для разрешения неоднозначностей. Иногда они могут быть неразрешимыми компилятором (см. Позже), который сообщит об ошибке, и вы должны устранить ее. В других случаях (как в примере) он просто использует "неправильный" метод, а не тот, который вы хотите. foo(a: String)
не компилируется, показывая, что тип ascription не приведен. Сравните это с предыдущей строкой, где компилятор доволен, но вы получите исключение, поэтому ошибка обнаруживается затем с типом ascription.
Вы получите неразрешимую неоднозначность, если вы также добавите метод
def foo(xs: Any*) {
println("vararg")
}
В этом случае первый и третий вызов foo не скомпилируется, так как компилятор не может решить, хотите ли вы вызывать foo с одним параметром Any или с varargs, поскольку оба они кажутся одинаково хорошими => you должен использовать тип ascription, чтобы помочь компилятору.
Изменить см. Также Какова цель ввода типа в Scala?
Программирование в Scala описывает это немного подробнее в главе 15 - Классы дел и сопоставление с образцом.
По сути, вторая форма может использоваться как "Типизированный шаблон" в сопоставлении с шаблоном, давая 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*
способ делать вещи намеренно подталкивает вас к стилю сопоставления с образцом.
Я не уверен, какой шаблон более эффективен для простого приведения типов без теста.
Есть пример различия:
- Тип cast (asInstanceOf) - это операция во время выполнения с возможным исключением во время выполнения.
- По сути, Ascription - это просто приведение, выполненное во время компиляции.
Пример:
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
Мы также можем вызвать метод, используя несколько отправлений:
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)