2011-08-14 19 views
17

Próbuję napisać metodę, która rzuca wartość Any na określony typ i zwraca opcję zamiast rzucania wyjątku, takiego jak instanceOf. Scala nie zachowuje się jak mam oczekiwać go:Pisanie ogólnej funkcji rzutowania Scala

def cast[A](value: Any): Option[A] = 
{ 
    try 
    { 
    Some(value.asInstanceOf[A]) 
    } catch 
    { 
    case e: Exception => None 
    } 
} 

Test:

val stringOption: Option[String] = cast[String](2) 
stringOption must beNone 

zakończy się błędem

java.lang.Exception: 'Some(2)' is not None 

ktoś ma pomysł dlaczego?

+0

Przesyłanie wartości całkowitych do łańcucha znaków powinno prowadzić do wyjątku, a metoda powinna zwrócić wartość None, ale tak nie jest. Używam scala 2.9.0-1 –

+0

Tak, zwraca Some (2), ale ... nie. Próba 'get' wartości powoduje wyjątek, ale' getOrElse' jest w porządku. –

+0

Tak, oczekiwałem, że wyjątek stanie się w metodzie rzutowania. –

Odpowiedz

21

Erasure deszcze na swojej parady tutaj. Dlatego w czasie wykonywania typ A nie jest już znany, a asInstanceOf[A] jest kompilowany do trybu bez operacji. Po prostu sprawia, że ​​kompilator uważa, że ​​wynikowa wartość jest typu A, ale w rzeczywistości nie jest to zapewnione w środowisku wykonawczym.

Możesz używać manifestów Scali do obejścia tego. Niestety postępowanie JVM z prymitywnymi typami/boksami zmusza nas do wykonania dodatkowej pracy.

Następujące prace, mimo że nie obsługują "słabej zgodności" typów, co oznacza np. Int nie jest uważany za długi, więc cast[Long](42) zwracazwraca.

def cast[A : Manifest](value: Any): Option[A] = { 
    val erasure = manifest[A] match { 
    case Manifest.Byte => classOf[java.lang.Byte] 
    case Manifest.Short => classOf[java.lang.Short] 
    case Manifest.Char => classOf[java.lang.Character] 
    case Manifest.Long => classOf[java.lang.Long] 
    case Manifest.Float => classOf[java.lang.Float] 
    case Manifest.Double => classOf[java.lang.Double] 
    case Manifest.Boolean => classOf[java.lang.Boolean] 
    case Manifest.Int => classOf[java.lang.Integer] 
    case m => m.erasure 
    } 
    if(erasure.isInstance(value)) Some(value.asInstanceOf[A]) else None 
} 
+0

to nie prawda - asInstanceOf zawsze kompiluje do operacji checkcast dla nieparzystych typów i generycznych – dk14

5

Jest to spowodowane usunięciem typu. W środowisku uruchomieniowym A w Option[A] nie jest znana, więc można przechowywać Some(3) w zmiennej typu Option[String].

Wyjątkiem będą występować, gdy wartość wewnątrz opcja jest dostępna:

scala> val result = cast[String](2) 
result: Option[String] = Some(2) 

scala> result.get 
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 
     at .<init>(<console>:10) 
     at .<clinit>(<console>) 
     // ... 
+0

Dlaczego 'getOrElse (42)' nie generuje tego wyjątku, ale ocenia na 2? –

+2

Typ zwracany przez "Option [A] .getOrElse [B]" musi być nadtypem zarówno "A", jak i "B". W przypadku 'String' i' Int', typem zwracanym jest 'Any', i możesz oczywiście rzucić' 2' na 'Any'. Gdybyś spróbował 'getOrElse (" 42 ")' dostałbyś 'ClassCastException', ponieważ typem zwracanym byłby' String'. –

+0

Ma sens, więc nie ma łatwego sposobu na napisanie takiej metody w sposób ogólny? –

2

zrobiłem prawie to samo, tylko teraz, ze Scala 2.10 TypeTags (ze względu na rodzaj skasowaniem) i ValidationNEL z scalaz:

import scala.reflect.runtime.universe._ 

def as[T: TypeTag](term: Any): ValidationNEL[String, T] = 
    if (reflect.runtime.currentMirror.reflect(term).symbol.toType <:< typeOf[T]) 
    term.asInstanceOf[T].successNel[String] 
    else 
    ("Cast error: " + term + " to " + typeOf[T]).failNel[T] 

z opcją zamiast Walidacja będzie to wyglądać tak :

def as[T: TypeTag](term: Any): Option[T] = 
    if (reflect.runtime.currentMirror.reflect(term).symbol.toType <:< typeOf[T]) 
    Some(term.asInstanceOf[T]) 
    else 
    None 

dostałem infos tutaj: How to know if an object is an instance of a TypeTag's type?, Runtime resolution of type arguments using scala 2.10 reflection

Powiązane problemy