2012-10-17 86 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