Scala asInstanceOf с параметризованными типами - PullRequest
30 голосов
/ 14 июля 2011

Я хотел бы написать функцию, которая приводит к типу A, где A может быть, например, List [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

Есть ли способ, которым я могу использовать манифесты, чтобы эта работа работала правильно?Спасибо!

Ответы [ 5 ]

18 голосов
/ 14 июля 2011

К сожалению, это неотъемлемое ограничение asInstanceOf. Я действительно удивлен, увидев, что скародок упомянул это в подробностях :

Обратите внимание, что успех приведения во время выполнения - это семантика стирания по модулю Скалы. Поэтому выражение 1.asInstanceOf[String] будет выбрасывать ClassCastException во время выполнения, в то время как выражение List(1).asInstanceOf[List[String]] не будет. В последнем примере, поскольку аргумент типа удаляется как часть компиляции, невозможно проверить, принадлежит ли содержимое списка запрошенному типу.

Если вы в основном обеспокоены неудачным быстрым при неверном приведении для traversable, что, вероятно, будет основной проблемой при возврате содержимого из вашего интерфейса 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, чтобы продолжить приведение, пока нет больше параметров типа ...

12 голосов
/ 14 июля 2011

Вы действительно правы - стирание типа означает, что вы не можете «кастовать» таким образом, чтобы различать, например, 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]

Вам нужны усовершенствованные дженерики , использующие манифесты

def cast[A <: AnyRef : Manifest](a : Any) : A 
  = manifest[A].erasure.cast(a).asInstanceOf[A]

В то время как окончательный состав стирается до AnyRef, по крайней мере, у вас должен быть правильный Class[_] instance (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))
5 голосов
/ 04 апреля 2014

Вы можете использовать бесформенный типизируемый от Майлза Сабина:

Приведение типа с использованием параметра типа

Во многих случаях оно обрабатывает удаление,хотя только конкретные:

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

2 голосов
/ 18 февраля 2013

Рассмотрим это решение:

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)

Преимущество состоит в том, что:

  1. Это типобезопасно - компилятор сообщит вам, если тип не может быть приведен
  2. Вы можете определить правила приведения, предоставив соответствующие импликации

Теперь вы можете использовать его так:

scala>  cast(new java.util.Date())
res9: Long = 1361195427192

scala>  cast("123")
res10: Int = 123

РЕДАКТИРОВАТЬ

IЯ потратил некоторое время и написал этот расширенный код.Сначала позвольте мне показать, как его использовать:

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]
^

Довольно мило?Смотрите магию скалы:

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)
}
2 голосов
/ 14 июля 2011

Да, проблема возникает из-за стирания типа.Если вы попробуете

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])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...