0

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!)
0

2 Answers 2

2

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)
}
Sign up to request clarification or add additional context in comments.

Comments

1

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....

1 Comment

The notion of equality in this case is best modeled by implementing an instance of the Eq typeclass. Changing the semantics of equality for a class, especially a case class, is very dangerous.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.