Scala:扩展地图并定义+

时间:2012-10-17 05:45:39

标签: scala generics map

我正在尝试编写一个小包装类,以使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 )
    }

}

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

3 个答案:

答案 0 :(得分:5)

错误的直接原因是您的+只接受JsonElement,而特征中的+需要一个上限为JsonElement的类型参数。

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

原因是(正如@Frank的答案所指出的),Map在其值参数中是协变的,即如果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 -> 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标准的热情拥抱库而是扩展mutable.Map,它已经为您precisely that。如果您的Java类型实现了java.util.Map接口,那么scala.collection.JavaConversions中甚至还有现成的包装器和隐式转换。

我不知道您要对自定义Map尝试做什么,但很可能延长Map根本不可能({3}在Scala集合库的标准介绍中实现了一个新的数据结构),你宁愿在大多数代码中处理Scala映射,然后提供隐含的例如将Map转换为边界处的GSON等效值。

答案 1 :(得分:0)

错误非常详细,并且重点:您尝试覆盖不在基类中的某些内容,并且您没有实现所需的方法。

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

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

答案 2 :(得分:0)

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

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

因此,请考虑在构造函数中克隆JsonObject并公开inner作为返回JsonObject的克隆副本而不是内部对象的方法。通过这种方式,可以保证GsonMap的不变性。