2015-12-10 20 views
8

Stavo cercando di rispondere alla domanda this, come pensavo di conoscere la risposta. Risulta, non sapevo abbastanza:/Perché talvolta `.asInstanceOf` lancia, e talvolta no?

Ecco un test che ho fatto:

class Inst[T] { 
    def is(x: Any) = scala.util.Try { as(x) }.isSuccess 
    def as(x: Any): T = x.asInstanceOf[T] 
} 

scala> new Inst[String].is(3) 
res17: Boolean = true 

scala> new Inst[String].as(3) 
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 
... 33 elided 

cosa sta succedendo qui? Perché solo la seconda chiamata al lancio as, ma non la prima?

risposta

10

Questo perché l'eccezione del cast di classe viene lanciata solo quando si fa qualcosa con il valore, chiamare un metodo su di esso dopo il cast. Ad esempio, nel REPL avresti una chiamata toString nel secondo caso. Nota:

new Inst[String].as(3);()   // nothing happens 
new Inst[String].as(3).toString;() // exception 

Il motivo per cui questo prende il passo in più è che Inst[T] è generico con il tipo di parametro T che viene cancellata in fase di esecuzione; solo quando il sito di chiamata (che ha una conoscenza statica di tipo T) tenta di chiamare un metodo sul risultato, si verifica il controllo del tipo effettivo.


Per la vostra domanda di follow-up, toString è definita su qualsiasi oggetto e dal T è generico si dispone di un numero intero in scatola (<: AnyRef) e toString e println riescono all'interno del metodo is. Così un altro esempio in cui l'Try sarebbe fallire è questo:

class Inst[T] { 
    def foo(x: Any)(implicit ev: T <:< String) = scala.util.Try { 
    ev(as(x)).reverse 
    }.isSuccess 
} 

new Inst[String].foo(3) // false! 
+0

No, questo non sembra per spiegare molto: ho cambiato la definizione di 'is' in:' def is (x: Any) = scala.util.Try {as (x) .toString} .isSuccess', e restituisce ancora 'true' (cioè, il il cast non getta). Anche questo 'def è (x: Any) = scala.util.Try {println (as (x) .toString)} .isSuccess;' stampa felicemente "3" e restituisce true: -/ – Dima

+0

Vedere la mia modifica –

+0

Ah, ora ha senso, grazie! 'is' non sa cosa sia' T', quindi tratta l'argomento come 'Any'. Ho provato questo: 'tratto Foo {def foo = ??? } class Inst [T <: Foo] {def is (x: Any) = scala.util.Try {as (x) .foo} .isSuccess; def as (x: Any): T = x.asInstanceOf [T]; } '. Ora 'new Inst [Foo] .is (3)' restituisce 'false' come previsto. – Dima

2

Mentre @ 0 __ 's risposta spiega il motivo per cui non funziona, ecco come farlo funzionare:

class Inst[T](implicit tag: scala.reflect.ClassTag[T]) { 
    def is(x: Any) = tag.runtimeClass.isInstance(x) 
    // scala.util.Try { as(x) }.isSuccess will work as well 
    def as(x: Any): T = tag.runtimeClass.cast(x).asInstanceOf[T] 
} 

object Main extends App { 
    println(new Inst[String].is(3)) 
    println(new Inst[String].as(3)) 
} 


false 
java.lang.ClassCastException: Cannot cast java.lang.Integer to java.lang.String 
    at java.lang.Class.cast(Class.java:3369) 
... 
Problemi correlati