[英]Type erasure, generics and existential types
我有一個List
的參數類型, List[Field[_]]
我想是這樣的:
sealed trait FieldType[K] { val name: String }
sealed trait StringField extends FieldType[String]
case object publisher extends StringField { val name = "publisher" }
// ...
trait Field[K] {
val field: FieldType[K]
val value: K
}
case class Publisher(value: String) extends Field[String] { val field = publisher }
// ...
def get[K](l: List[Field[_]], key: FieldType[K]) : Option[K] =
l match {
case Nil => None
case (a:Field[K]) :: rest => Some(a.value)
case (a:Field[_]) :: rest => get(rest, key)
}
這不起作用,因為K被刪除了。 我嘗試過打字標簽,但我必須承認自己迷路了。 任何快速的方法來獲得我想要的?
是的,您可以使用類型標記來使編譯器生成已擦除的類型信息。 根據Scala文檔 ,有三種獲取標簽的方法。
通過添加ClassTag
類型的隱式證據參數,如果編譯器找不到合適的隱式值,它將生成缺少的類型信息。 只要有證據,我們就可以獲取運行時類並將其與您Field
value
的運行時類進行比較。
def get[A](l: List[Field[_]])(implicit evidence: ClassTag[A]): Option[A] =
l match {
case Nil => None
case (a: Field[A]) :: rest if evidence.runtimeClass == a.value.getClass =>
Some(a.value)
case a :: rest =>
get[A](rest)
}
但是,請注意,在此示例中, get[Int]
將產生一個帶有運行時類Int
的ClassTag
,而value.getClass
將返回一個java.lang.Integer
給定case class Other(value: Int) extends Field[Int]
,將產生以下結果:
def main(args: Array[String]): Unit = {
println(get[String](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // Some(foo)
println(get[Integer](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // Some(4)
println(get[Int](Other(4) :: Other(2) :: Publisher("foo") :: Nil)) // None
}
請注意,我刪除了參數key
,因為它沒有作用。 如果鍵應該是唯一的,那么我建議不要對鍵進行匹配,而不是檢查類型標簽,從而不使用反射:
def get[A](l: List[Field[_]], key: FieldType[A]): Option[A] = l match {
case Nil => None
case a :: rest if a.field == key => Some(a.value.asInstanceOf[A])
case a :: rest => get(rest, key)
}
由於l
的類型為List[Field[_]]
,所以a
的類型為Field[_]
,這意味着a.value
具有存在性類型,即,編譯器不知道它的類型為A
然而,由於之間的關系a.key
和a.value
,我們知道,如果a.field
是類型FieldType[A]
則value
的類型是A
,所以這種特殊類型的投是安全的(只要你做不更改代碼)。
您必須使用類型強制轉換表示設計中的缺陷,這是完全正確的,因此最好的解決方案是重新設計Field
或list l
。 實際上,我會問自己為什么您需要將各種不同類型的Field
放入列表中,然后稍后提取某種類型的Field
? List
可能是錯誤的數據結構? Map[FieldType[_], List[Field[_]]
是存儲字段的更好選擇嗎? 還是Map[Class[_], List[Field[_]]
? 還是自定義數據結構?
請注意,您可以通過以下方式擺脫asInstanceOf
:
def get[A](l: List[Field[_]], key: FieldType[A]): Option[A] = l match {
case Nil => None
case (a: Field[A]) :: rest if a.field == key => Some(a.value)
case a :: rest => get(rest, key)
}
但這不能為您提供更多的靜態安全性,因為類型擦除使Field[A]
與任何通用的Field
版本匹配。 我會說這種變體更糟,因為它使顯式類型強制轉換為隱式,因此此代碼更容易出錯。
引入了三種類型的標簽來代替Manifest
:
TypeTag
WeakTypeTag
ClassTag
從ScalaDoc :
ClassTag是scala.reflect.api.TypeTags#TypeTags的一種較弱的特殊情況,因為它們僅包裝給定類型的運行時類,而TypeTag包含所有靜態類型信息。
雖然您也可以使用TypeTag
解決問題,但我認為比較運行時類就足夠了。
問題在於,存在性類型實際上可以是A
任何超類,因為列表l
可以包含各種Field
。 在我給出的示例中,我們有一個Field[Int] :: Field[Int] :: Field[String] :: Field[Int]
,因此在這種情況下,存在類型必須是Any
,它是的最不常見的超類型Int
和String
。 換句話說:派生l: List[Field[_]]
的存在類型對您沒有任何好處l: List[Field[_]]
。
但是,您實際要做的是在列表中找到第一個元素,其value
是A
類型。 因為刪除了A
,所以獲取有關其運行時類型的信息的唯一方法是將信息作為附加參數傳遞,例如,使用隱式ClassTag
證據。 現在剩下的就是找出哪個元素具有對應類型的值,因此a.value.getClass == evidence.runtimeClass
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.