0

我正在尝试编写一个小型包装类,以使Gson 库对 Scala 更加友好。不幸的是,当我尝试按照我想要的方式进行时,我遇到了编译错误。

这是我到目前为止的代码:

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

}

现在,我知道该+方法与 Map 类中定义的不匹配。这就是问题所在,真的。我希望该+方法接受JsonElements 并返回 a 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

有什么建议吗?


更新:

正如下面所建议的,这是我尝试过的变体之一:

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

我对>:运算符的理解是 T 必须是 JsonElement 的父级,我认为这不是我要找的。在这种情况下,此映射只能包含 JsonElements 的实例,因此不适合放入 JsonElements 的父项。

4

3 回答 3

5

您的错误的直接原因是您+只接受 JsonElement,而+trait 中的 期望类型参数的上限为JsonElement.

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

原因是(正如@Frank 的回答中所指出的)是 Map 在其 value 参数中是协变的,即如果Child是 的子类型Parent,Map[String,Parent]将是 的超类型Map[String, Child],并且此add定义允许您“向上添加”到 a 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 -> SubElement@6a63afa4)

scala> m + ("bar" -> new Element)
res0: scala.collection.immutable.Map[java.lang.String,Element] = Map(foo -> SubElement@2e7ff81e, bar -> Element@654ab15b)

scala> m + ("bar" -> new Element) + ("baz" -> "Text")
res1: scala.collection.immutable.Map[java.lang.String,java.lang.Object] = Map(foo -> SubElement@6a63afa4, bar -> Element@233d0d04, baz -> Text)

如果您尝试Map在可变支持对象上实现不可变特征,则必须自己提供这种“向上转换”,或者您可以屈服于 Scala 标准库的热情拥抱,而改为 extend mutable.Map,这已经做到了正是为您服务。如果您的 Java 类型实现了java.util.Map接口,那么在scala.collection.JavaConversions.

我不知道你想用你的 custom 做什么Map,但很可能扩展Map根本不是可行的方法(Scala 集合库的标准介绍中扩展地图的示例实现了一个新数据结构),您宁愿在您的大部分代码中处理 Scala 映射,然后提供隐式,例如将映射转换为边界处的 GSON 等价物。

于 2012-10-17T06:32:49.360 回答
0

该错误非常详细且切中要害:您尝试覆盖不在基类中的内容并且您没有实现所需的方法。

就解决方案而言,您基本上错过的是Map使用的方差注释。查看该课程的ScalaDoc,Map将看到:Map[A, +B]. 这一点+导致你的问题。

要了解发生了什么,我建议您阅读协方差,然后了解为什么该+方法具有不同的类型签名并且不返回a Map[A, B],而是返回 a Map[A, B1], where B1 >: B。您应该这样做,因为这不仅可以让您保留不变JsonElement对象的映射,还可以在您拥有子类时从协方差中获益。

于 2012-10-17T06:34:40.623 回答
0

“+”方法需要具有以下签名:+[B1 >: B](kv: (A, B1)): Map[A, B1]

更多的是观察而不是答案:您的 GSonMap 有一个构造函数,它接收 JsonObject 并在内部使用它。它还将 JsonObject 公开为公共字段。问题是 JsonObject 是可变的,并且由于您在 GsonMap 中公开它的方式,后者也变得可变(这是因为任何人都可以从外部修改 JsonObject)。

因此,请考虑在构造函数中克隆 JsonObject 并将其公开inner为返回 JsonObject 的克隆副本而不是内部对象的方法。这样就保证了 GsonMap 的不变性。

于 2012-10-17T09:32:29.383 回答