Scala:扩展地图并定义+

Nyc*_*cto 0 generics scala map

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

现在,我知道该+方法与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
Run Code Online (Sandbox Code Playgroud)

有关于此的任何建议吗?


更新:

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

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

但是,它也失败了:

[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
Run Code Online (Sandbox Code Playgroud)

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

the*_*mel 5

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

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

原因是(正如在@Frank的回答中所指出的),Map在其值参数中是协变的,即如果Child是子类型Parent,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)
Run Code Online (Sandbox Code Playgroud)

如果你试图Map在一个可变的支持对象上实现不可变的特征,你必须自己提供这个"up-casting",或者你可以放弃Scala标准库的热情拥抱,而是扩展mutable.Map,已经做了正是为了你.如果您的Java类型实现了java.util.Map接口,那么甚至还有现成的包装器和隐式转换scala.collection.JavaConversions.

我不知道你要用你的自定义做什么Map,但很可能扩展Map不是完全可行的方式(在标准介绍中将地图扩展到Scala集合库的例子实现了一个新数据您希望在大多数代码中处理Scala映射,然后提供隐式,例如将Map转换为边界处的GSON等效值.