簡體   English   中英

Scala asInstanceOf 與參數化類型

[英]Scala asInstanceOf with parameterized types

我想編寫一個轉換為類型 A 的 function,其中 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

有沒有辦法可以使用清單來使其正常工作? 謝謝!

不幸的是,這是asInstanceOf的固有限制。 看到 scaladoc詳細提到它,我真的很驚訝:

請注意,運行時強制轉換的成功是模 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 以繼續強制轉換,直到沒有更多類型參數...

您確實是正確的-例如,類型擦除意味着您不能以區分List[Int]List[String]的方式“投射”。 但是,您可以改進您正在執行的演員表,其中A被刪除,這意味着您無法區分IntString

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))

您可以使用 Miles Sabin 的shapeless的 Typeable:

使用類型參數進行類型轉換

它在許多情況下處理擦除,盡管只有特定的:

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

考慮這個解決方案:

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

編輯

我花了一些時間編寫了這個高級代碼。 首先讓我展示如何使用它:

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)
}

是的,問題是由於類型擦除而發生的。 如果你試試

val x = List(1,2,3)
val y = castToType[Int](x)

正如預期的那樣,立即拋出異常。 嘗試轉換為Array[String]甚至Array[Int]時也會發生同樣的情況。

我不認為你可以創建一個通用類型轉換器,它可以在 collections 和其他對象中輸入類型。 您需要為每個 object 類型創建一個轉換器。 例如:

def castToType[A](x: List[A]) = x.map(i => i.asInstanceOf[A])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM