簡體   English   中英

類型擦除,泛型和存在類型

[英]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]將產生一個帶有運行時類IntClassTag ,而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)
}

編輯:要從評論中解決您的問題:

asInstanceOf是必需的嗎? ...給我的印象是,不得不訴諸這種做法是一種不良習慣,應該避免/不安全。 那是對的嗎?

由於l的類型為List[Field[_]] ,所以a的類型為Field[_] ,這意味着a.value具有存在性類型,即,編譯器不知道它的類型為A 然而,由於之間的關系a.keya.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版本匹配。 我會說這種變體更糟,因為它使顯式類型強制轉換為隱式,因此此代碼更容易出錯。

我試圖使用TypeTag []和typeOf []代替ClassTag和runtimeClass,就像我在您鏈接的文檔頁面(以及其他地方)上找到的示例一樣。 有什么不同? 最重要的是,我想要有關Field [_]中的_的信息,那么,為什么哦,為什么ClassTag在A上?!

引入了三種類型的標簽來代替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 ,它是的最不常見的超類型IntString 換句話說:派生l: List[Field[_]]的存在類型對您沒有任何好處l: List[Field[_]]

但是,您實際要做的是在列表中找到第一個元素,其valueA類型。 因為刪除了A ,所以獲取有關其運行時類型的信息的唯一方法是將信息作為附加參數傳遞,例如,使用隱式ClassTag證據。 現在剩下的就是找出哪個元素具有對應類型的值,因此a.value.getClass == evidence.runtimeClass

暫無
暫無

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

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