Pregunta

What is the simplest way to convert a java.util.IdentityHashMap[A,B] into a subtype of scala.immutable.Map[A,B]? I need to keep keys separate unless they are eq.

Here's what I've tried so far:

scala> case class Example()
scala> val m = new java.util.IdentityHashMap[Example, String]()
scala> m.put(Example(), "first!")
scala> m.put(Example(), "second!")
scala> m.asScala // got a mutable Scala equivalent OK
res14: scala.collection.mutable.Map[Example,String] = Map(Example() -> first!, Example() -> second!)
scala> m.asScala.toMap // doesn't work, since toMap() removes duplicate keys (testing with ==)
res15: scala.collection.immutable.Map[Example,String] = Map(Example() -> second!)
¿Fue útil?

Solución

Here's a simple implementation of identity map in Scala. In usage, it should be similar to standard immutable map.

Example usage:

val im = IdentityMap(
  new String("stuff") -> 5,
  new String("stuff") -> 10)

println(im) // IdentityMap(stuff -> 5, stuff -> 10)

Your case:

import scala.collection.JavaConverters._
import java.{util => ju}

val javaIdentityMap: ju.IdentityHashMap = ???
val scalaIdentityMap = IdentityMap.empty[String,Int] ++ javaIdentityMap.asScala

Implementation itself (for performance reasons, there may be some more methods that need to be overridden):

import scala.collection.generic.ImmutableMapFactory
import scala.collection.immutable.MapLike

import IdentityMap.{Wrapper, wrap}

class IdentityMap[A, +B] private(underlying: Map[Wrapper[A], B])
  extends Map[A, B] with MapLike[A, B, IdentityMap[A, B]] {

  def +[B1 >: B](kv: (A, B1)) =
    new IdentityMap(underlying + ((wrap(kv._1), kv._2)))

  def -(key: A) =
    new IdentityMap(underlying - wrap(key))

  def iterator =
    underlying.iterator.map {
      case (kw, v) => (kw.value, v)
    }

  def get(key: A) =
    underlying.get(wrap(key))

  override def size: Int =
    underlying.size

  override def empty =
    new IdentityMap(underlying.empty)

  override def stringPrefix =
    "IdentityMap"
}

object IdentityMap extends ImmutableMapFactory[IdentityMap] {
  def empty[A, B] =
    new IdentityMap(Map.empty)

  private class Wrapper[A](val value: A) {
    override def toString: String =
      value.toString

    override def equals(other: Any) = other match {
      case otherWrapper: Wrapper[_] =>
        value.asInstanceOf[AnyRef] eq otherWrapper.value.asInstanceOf[AnyRef]
      case _ => false
    }

    override def hashCode =
      System.identityHashCode(value)
  }

  private def wrap[A](key: A) =
    new Wrapper(key)
}

Otros consejos

One way to handle this would be change what equality means for the class, e.g.

scala> case class Example() {
  override def equals( that:Any ) = that match {
    case that:AnyRef => this eq that
    case _ => false
  }
}
defined class Example

scala> val m = new java.util.IdentityHashMap[Example, String]()
m: java.util.IdentityHashMap[Example,String] = {}

scala> m.put(Example(), "first!")
res1: String = null

scala> m.put(Example(), "second!")
res2: String = null

scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._

scala> m.asScala
res3: scala.collection.mutable.Map[Example,String] = Map(Example() -> second!, Example() -> first!)

scala> m.asScala.toMap
res4: scala.collection.immutable.Map[Example,String] = Map(Example() -> second!, Example() -> first!)

Or if you don't want to change equality for the class, you could make a wrapper.

Of course, this won't perform as well as a Map that uses eq instead of ==; it might be worth asking for one....

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top