Scala: почему asInstanceOf может принимать параметр типа, а isInstanceOf — нет?

Вот надуманные эксперименты в REPL (scala 2.11):

scala> class Foo[T] {
     |   def as(x: Any) = x.asInstanceOf[T]
     | }

defined class Foo

scala> val foo = new Foo[String]
foo: Foo[String] = Foo@65ae6ba4

scala> val x: Any = 123
x: Any = 123

scala> foo.as(x)  // expected
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  ... 33 elided

scala> val y: Any = "abc"
y: Any = abc

scala> foo.as(y)
res1: String = abc


scala> class Bar[T] {
     |   def is(x: Any) = x.isInstanceOf[T]
     | }

<console>:12: warning: abstract type T is unchecked since it is eliminated by erasure
         def is(x: Any) = x.isInstanceOf[T]
                                        ^
defined class Bar

scala> val bar = new Bar[String]
foo: Foo[String] = Foo@1753acfe

scala> val x: Any = 123
x: Any = 123

scala> bar.is(x)  // unexpected
res2: Boolean = true

scala> val y: Any = "abc"
y: Any = abc

scala> bar.is(y)
res3: Boolean = true

Я знаю, что параметр типа довольно ограничен из-за стирания типа, но все еще смущен различным поведением между asInstanceOf и isInstanceOf здесь.

Интересно, есть ли у кого-нибудь представление об этом? Спасибо!


person QRush    schedule 10.12.2015    source источник
comment
Эти две операции очень разные, в то время как isInstanceOf — это логический параметр, который возвращает true/false (в вашем случае всегда true, потому что x имеет тип Any), asInstanceOf на самом деле пытается выполнить приведение вашей переменной к типу, который вы хотели, и, следовательно, java.lang.ClassCastException. На самом деле оба могут получить параметры Type, но кастинг должен быть законным.   -  person Avihoo Mamka    schedule 10.12.2015
comment
@AvihooMamka можно использовать asInstanceOf и посмотреть, получится ли. Почему isInstanceOf не реализован таким образом?   -  person n. 1.8e9-where's-my-share m.    schedule 10.12.2015
comment
Сгенерированный байтовый код вызывает Java instanceof. Технически isInstanceOf является частью отражения Java, где оно известно как instanceof.   -  person Avihoo Mamka    schedule 10.12.2015
comment
@н.м. asInstanceOf всегда будет успешным, пока вы не попытаетесь использовать его как неправильный тип, но нет универсального способа узнать это во время выполнения. То есть каждый тип терпит неудачу по-своему.   -  person Michael Zajac    schedule 10.12.2015
comment
@ m-z хм, тогда выглядит довольно бесполезно.   -  person n. 1.8e9-where's-my-share m.    schedule 10.12.2015


Ответы (1)


Ну, вы должны знать, что параметры типа недоступны во время выполнения, вся информация, которую они несут, может использоваться только компилятором. Теперь asInstanceOf — это просто приведение, оно нужно компилятору только для обеспечения совместимости типов, а во время выполнения оно вообще ничего не делает: ссылка — это ссылка, относящаяся к типу базового объекта.

isInstanceOf наоборот: компилятор ничего об этом не знает, это просто вызов функции. Он выполняется во время выполнения, чтобы проверить, относится ли данный объект к ожидаемому типу. Но параметр типа недоступен во время выполнения, так как же узнать, какой тип проверять? По этой причине ему нужен реальный аргумент Class.

person Dima    schedule 10.12.2015
comment
Точно. Если вы скомпилируете его с scalac -print, вызов asInstanceOf[T] будет удален, и вы увидите код: def as(x: Object): Object = x - person Archeg; 10.12.2015
comment
Я думаю, что это на самом деле немного глючит: def as(x:Any):Any = x.asInstanceOf[T] не выдает, если вы называете это as(5), даже если T является String - person Archeg; 10.12.2015
comment
@Archeg не уверен, что вы имеете в виду: я только что попробовал это: scala> new Foo[String].as(3) java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String ... 33 elided - person Dima; 10.12.2015
comment
По-видимому, foo.asInstanceOf[Bar] не будет вызывать сам по себе по причинам, которые я объяснил в своем ответе (вызов просто удаляется во время выполнения), что происходит в REPL, когда вы делаете new Foo[String].as(3), так это то, что сам REPL пытается получить доступ к возвращенному объекту, чтобы напечатать его out и обрабатывает его как String (потому что он статически знает, что это то, что должно возвращать Foo[Sring].as), и это выбрасывает. - person Dima; 10.12.2015
comment
Я не думаю, что это как-то связано с REPL. Если я запускаю тот же код с scalac, он все равно ведет себя так же. Если я помещаю возвращаемое значение as в T - он выдает, но если я помещаю возвращаемое значение Any - это не так. Я думаю, что понимаю, почему это происходит, но это все еще ошибка, - не должно иметь значения, какой тип возвращаемого значения. - person Archeg; 10.12.2015
comment
Можете ли вы показать код? Как именно вы это называете, и что вы делаете с результатом? - person Dima; 10.12.2015
comment
class Foo[T] { def as(x: Any):Any = x.asInstanceOf[T] } val f = new Foo[String]() val b = f.as(5) Этот компилируется и успешно запускается. Если поменять :Any на :T - перестанет работать - person Archeg; 10.12.2015
comment
Верно. Потому что, когда вы присваиваете результат as b, он статически знает, что он должен быть строкой, и расстраивается, что это не так. Попробуйте что-нибудь вроде val b = { f.as(5); () } - person Dima; 10.12.2015
comment
Вы правы, теперь я это вижу. Очень сложная ситуация с asInstanceOf здесь, обычно не ожидаешь такого странного поведения от такого простого метода. Спасибо за разъяснение - person Archeg; 10.12.2015
comment
Да дело в том, что это не совсем метод. Больше похоже на директиву компилятора. - person Dima; 10.12.2015
comment
Это похоже на метод, но это не так. И это вносит много путаницы. Было бы лучше, если бы это выглядело как обычное приведение типов или что-то вроде ключевого слова as в C#. - person Archeg; 10.12.2015
comment
Спасибо за ответ, а также за обсуждения! Перед публикацией вопроса я также проверил их реализации, но, к сожалению, не нашел там ничего полезного. Теперь это выглядит менее запутанно и проще для понимания, если думать об asInstanceOf как о директиве компилятора (для того, чтобы компилятор вставлял соответствующее приведение и обеспечивал совместимость типов во время компиляции) вместо простого метода. - person QRush; 11.12.2015