Scala asInstanceOf с параметризованными типами
Я хотел бы написать функцию, которая отбрасывает тип A, где A может быть, например, Список [Int] или более сложный параметризованный тип, такой как Map [Int, List [Int]].
def castToType[A](x: Any): A = {
// throws if A is not the right type
x.asInstanceOf[A]
}
Прямо сейчас, из-за стирания типа (я считаю), код весело работает, даже если тип неверен. Ошибка проявляется только при доступе, с ClassCastException.
val x = List(1, 2, 3)
val y = castToType[List[String]](x)
y(0) --> throws java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Есть ли способ использовать манифесты для правильной работы? Спасибо!
Ответы
Ответ 1
К сожалению, это связано с присущим ему ограничением asInstanceOf
. Я действительно удивлен, увидев, что скаладок упоминает об этом в подробнее:
Обратите внимание, что успех приведения во время выполнения по модулю Scala семантика стирания. Поэтому выражение 1.asInstanceOf[String]
будет вызывать ClassCastException
во время выполнения, тогда как выражение List(1).asInstanceOf[List[String]]
не будет. В последнем примере, поскольку аргумент типа удаляется как часть компиляции, невозможно проверить, является ли содержимое списка запрошенным типом.
Если вы в основном обеспокоены ошибкой в неправильном использовании для прохождения, что, вероятно, будет основной проблемой при возвращении материала из вашего интерфейса DB/memcached, я играл вокруг, заставляя отливку головы для объектов, проходящих через объект:
def failFastCast[A: Manifest, T[A] <: Traversable[A]](as: T[A], any: Any) = {
val res = any.asInstanceOf[T[A]]
if (res.isEmpty) res
else {
manifest[A].newArray(1).update(0, res.head) // force exception on wrong type
res
}
}
На простом примере он работает:
scala> val x = List(1, 2, 3): Any
x: Any = List(1, 2, 3)
scala> failFastCast(List[String](), x)
java.lang.ArrayStoreException: java.lang.Integer
[...]
scala> failFastCast(List[Int](), x)
res22: List[Int] = List(1, 2, 3)
Но не на более сложном:
val x = Map(1 -> ("s" -> 1L)): Any
failFastCast(Map[Int, (String, String)](), x) // no throw
Интересно, есть ли способ рекурсивного сверления вниз в A, чтобы продолжить кастинг, пока не будет больше параметров типа...
Ответ 2
Вы действительно правы - стирание типа означает, что вы не можете "отличать" таким образом, чтобы различать List[Int]
и List[String]
, например. Тем не менее, вы можете улучшить выполняемый литой, в результате чего A
стирается таким образом, что вы не можете различать Int
и String
:
def cast[A](a : Any) = a.asInstanceOf[A]
//... is erased to
def erasedCast(a : Any) = a.asInstanceOf[Any]
Что вам нужно, это reified generics, используя манифесты
def cast[A <: AnyRef : Manifest](a : Any) : A
= manifest[A].erasure.cast(a).asInstanceOf[A]
Пока окончательное приведение стирается до AnyRef
, по крайней мере, у вас должен быть правильный экземпляр Class[_]
(manifest.erasure
), чтобы получить правильный тип верхнего уровня. В действии:
scala> cast[String]("Hey")
res0: String = Hey
scala> cast[java.lang.Integer]("Hey")
java.lang.ClassCastException
at java.lang.Class.cast(Class.java:2990)
at .cast(<console>:7)
at .<init>(<console>:9)
scala> cast[List[String]](List("Hey"))
res2: List[String] = List(Hey)
scala> cast[List[Int]](List("Hey"))
res3: List[Int] = List(Hey)
Мой совет - не использовать вложенное отражение, чтобы решить, действительно ли цель была List[Int]
: это, как правило, невозможно. Для чего следует следующее возвращение?
cast[List[Int]](List[AnyVal](1, 2))
Ответ 3
Вы можете использовать shapeless Тип из Miles Sabin:
Набирать тип с использованием параметра типа
Он обрабатывает стирание во многих случаях, хотя только определенные:
scala> import shapeless._; import syntax.typeable._
import shapeless._
import syntax.typeable._
scala> val x = List(1, 2, 3)
x: List[Int] = List(1, 2, 3)
scala> val y = x.cast[List[String]]
y: Option[List[String]] = None
Чтобы увидеть набор случаев, с которыми он справляется, вы можете обратиться к его источнику:
https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala
Ответ 4
Да, проблема возникает из-за стирания типа. Если вы попробуете
val x = List(1,2,3)
val y = castToType[Int](x)
Исключение выбрано сразу, как и ожидалось. То же самое происходит при попытке применить к Array[String]
или даже Array[Int]
.
Я не думаю, что вы можете создать универсальный конвертер типов, который будет работать внутри коллекций и других объектов. Вам потребуется создать конвертер для каждого типа объекта. Например:
def castToType[A](x: List[A]) = x.map(i => i.asInstanceOf[A])
Ответ 5
Рассмотрим это решение:
trait -->[A, B] {
def ->(a: A): B
}
implicit val StringToInt = new -->[String, Int] {
def ->(a: String): Int = a.toInt
}
implicit val DateToLong = new -->[java.util.Date, Long] {
def ->(a: java.util.Date): Long = a.getTime
}
def cast[A,B](t:A)(implicit ev: A --> B):B= ev.->(t)
Преимущество состоит в том, что:
- Это безопасный тип - компилятор скажет вам, не может ли тип быть литым
- Вы можете определить правила кастинга, указав правильные импликации
Теперь вы можете использовать его так:
scala> cast(new java.util.Date())
res9: Long = 1361195427192
scala> cast("123")
res10: Int = 123
ИЗМЕНИТЬ
Я потратил некоторое время и написал этот расширенный код. Сначала позвольте мне показать, как его использовать:
scala> "2012-01-24".as[java.util.Date]
res8: java.util.Date = Tue Jan 24 00:00:00 CET 2012
scala> "2012".as[Int]
res9: Int = 2012
scala> "2012.123".as[Double]
res12: Double = 2012.123
scala> "2012".as[Object] // this is not working, becouse I did not provide caster to Object
<console>:17: error: could not find implicit value for parameter $greater: -->[String,Object]
"2012".as[Object]
^
Довольно приятно? См. Магию scala:
trait -->[A, B] {
def ->(a: A): B
}
implicit val StringToInt = new -->[String, Int] {
def ->(a: String): Int = a.toInt
}
implicit val StringToDate = new -->[String, java.util.Date] {
def ->(a: String): java.util.Date = (new java.text.SimpleDateFormat("yyyy-MM-dd")).parse(a)
}
implicit val StringToDouble = new -->[String, Double] {
def ->(a: String): Double = a.toDouble
}
trait AsOps[A] {
def as[B](implicit > : A --> B): B
}
implicit def asOps[A](a: A) = new AsOps[A] {
def as[B](implicit > : A --> B) = > ->(a)
}