2012-10-17 84 views
0

我想寫一個小的包裝類,使Gson library多一點的斯卡拉友好。不幸的是,當我嘗試以我想要的方式進行操作時,我遇到了編譯錯誤。斯卡拉:擴展地圖和定義+

這是迄今爲止我已經得到了代碼:

package com.test 

import com.google.gson.{JsonObject, JsonElement} 
import scala.collection.Iterator 
import scala.collection.immutable.Map 

case class GsonMap (private val inner: JsonObject = new JsonObject) 
    extends Map[String, JsonElement] { 

    /** {@inheritDoc} */ 
    override def iterator: Iterator[(String, JsonElement)] 
     = new Iterator[(String, JsonElement)] { 
      private val entries = inner.entrySet.iterator 
      override def hasNext: Boolean = entries.hasNext 
      override def next: (String, JsonElement) = { 
       val elem = entries.next 
       (elem.getKey, elem.getValue) 
      } 
     } 

    /** 
    * Returns a clone of the inner JsonObject 
    */ 
    private def cloneInner: JsonObject = { 
     val result = new JsonObject() 
     iterator.foreach { (item) => result.add(item._1, item._2) } 
     result 
    } 

    /** {@inheritDoc} */ 
    override def + (kv: (String, JsonElement)): GsonMap = { 
     val cloned = cloneInner 
     cloned.add(kv._1, kv._2) 
     GsonMap(cloned) 
    } 

    /** {@inheritDoc} */ 
    override def get(key: String): Option[JsonElement] 
     = Option(inner.get(key)) 

    /** {@inheritDoc} */ 
    override def - (key: String): GsonMap = { 
     val cloned = cloneInner 
     cloned.remove(key) 
     GsonMap(cloned) 
    } 

} 

現在,我知道+方法不匹配是地圖類中定義。這就是問題所在,真的。我想+方法接受JsonElement s並返回GsonMap,但我不確定如何使這項工作。我試圖在這一點上有一些變化,但沒有運氣

以供參考,這是編譯錯誤我收到:

[info] Compiling 1 Scala source to target/scala-2.9.2/classes... 
[error] src/main/scala/GsonMap.scala:7: class GsonMap needs to be abstract, since method + in trait Map of type [B1 >: com.google.gson.JsonElement](kv: (String, B1))scala.collection.immutable.Map[String,B1] is not defined 
[error] case class GsonMap (val inner: JsonObject = new JsonObject) 
[error]   ^
[error] src/main/scala/GsonMap.scala:31: method + overrides nothing 
[error]  override def + (kv: (String, JsonElement)): GsonMap = { 
[error]    ^
[error] two errors found 

任何意見,在那裏這件事嗎?


UPDATE:

正如下面還建議,這是我試過的變化之一:

override def +[T >: JsonElement] (kv: (String, T)): GsonMap = { 
    val cloned = cloneInner 
    cloned.add(kv._1, kv._2) 
    GsonMap(cloned) 
} 

但是,它也失敗了:

[info] Compiling 1 Scala source to target/scala-2.9.2/classes... 
[error] /src/main/scala/GSON.scala:33: type mismatch; 
[error] found : T 
[error] required: com.google.gson.JsonElement 
[error]   cloned.add(kv._1, kv._2) 
[error]        ^
[error] one error found 

我對>:運營商的理解是th在T必須是JsonElement的父母,我不認爲是我正在尋找的。在這種情況下,此映射只能包含JsonElements的實例,因此放入JsonElements的父項是不合適的。

回答

5

錯誤的直接原因是您的+只接受JsonElement,而性狀中的+需要一個類型參數,其上限爲JsonElement

override def +[T >: JsonElement] (kv: (String, T)): GsonMap = { 
    val cloned = cloneInner 
    cloned.add(kv._1, kv._2) 
    GsonMap(cloned) 
} 

的原因是(如@指出坦率的回答)是地圖是其價值的說法,即協如果ChildParent一個亞型,Map[String,Parent]將是Map[String, Child]父類型,這add定義讓你「上加」到Map

scala> class Element; 
defined class Element 

scala> class SubElement extends Element; 
defined class SubElement 

scala> val m = Map("foo"-> new SubElement) 
m: scala.collection.immutable.Map[java.lang.String,SubElement] = Map(foo -> [email protected]) 

scala> m + ("bar" -> new Element) 
res0: scala.collection.immutable.Map[java.lang.String,Element] = Map(foo -> [email protected], bar -> [email protected]) 

scala> m + ("bar" -> new Element) + ("baz" -> "Text") 
res1: scala.collection.immutable.Map[java.lang.String,java.lang.Object] = Map(foo -> [email protected], bar -> [email protected], baz -> Text) 

如果你想實現一個可變的支持對象的不可變Map特質,你必須提供這個「上鑄」自己,或你可以屈服於溫暖的懷抱斯卡拉標準庫,而不是擴展mutable.Map,它已經爲你做precisely that。如果您的Java類型實現了java.util.Map接口,那麼在scala.collection.JavaConversions中甚至有現成的包裝和隱式轉換。

我不知道你想與您的自定義Map做什麼,但它是相當有可能延長Map是不是要在所有(在example for extending maps標準介紹到Scala的集合庫實現的方式一個新的數據結構),而你更願意在大部分代碼中處理Scala地圖,然後提供一個隱含的例如將地圖轉換爲邊界處的GSON等效物。

+0

感謝您的意見!我曾嘗試過類似的原創,但沒有運氣。我更新了我的問題,以包含它和它導致的編譯錯誤。 – Nycto

+0

我添加了一些背景。 – themel

+0

感謝您的幫助和廣泛響應。我懷疑是這種情況 – Nycto

0

錯誤是非常詳細的和重點:您嘗試覆蓋不在基類中的東西,並且您還沒有實現所需的方法。

就解決方案而言,您基本錯過的是Map使用的差異註釋。請參閱Map課程的ScalaDoc,您將看到:Map[A, +B]。這個小小的+正在導致你的問題。

要理解這是怎麼回事,我建議你閱讀協起來,然後明白爲什麼+方法有不同類型的簽名和不回報Map[A, B],而是一個Map[A, B1],其中B1 >: B。你應該這樣做,因爲這樣也可以讓你不僅保留一個不變的JsonElement對象的地圖,而且在你有子類時從協變中獲益。

+0

感謝您的反饋。我以前做過關於協變和逆變的一些閱讀,但我只是努力應用它在這種情況下。正如我在另一條評論中所說的,我仍然在形成我的心理模型。在這種情況下,我認爲我需要朝正確的方向推進。 – Nycto

0

的「+」方法需要具備以下特徵:+[B1 >: B](kv: (A, B1)): Map[A, B1]

更多的不是答案的觀察:你GSonMap具有接收的JSONObject並使用它內部的構造函數。它還將JsonObject公開爲公共領域。問題是JsonObject是可變的,並且由於你在GsonMap中公開它的方式,後者也變得可變(這是因爲任何人都可以從外部修改JsonObject)。

因此,請考慮在構造函數中克隆JsonObject並將inner作爲返回克隆的JsonObject副本而不是內部對象的方法公開。通過這種方式可以保證GsonMap的不變性。

+0

我知道簽名不匹配,我想我仍然在斯卡拉鞏固我的類型系統的心智模型。我更新了一些關於我嘗試的更具體變化的更多細節。關於「內部」變量的暴露:感謝CR!我在制定這篇文章時只是想念我。我更新了這個問題,以便沒有人複製這個代碼(儘管它仍然被破壞)並且具有相同的錯誤。 – Nycto