Map of with object references as keys?

2019-02-18 20:50发布

问题:

I have an object with stores information about specific instances. For that, i would like to use a Map, but as the keys are not by-reference (they aren't, right?) but as hashes provided by the getHashCode method. For better understanding:

import collection.mutable._
import java.util.Random

object Foo {
    var myMap = HashMap[AnyRef, Int]()

    def doSomething(ar: AnyRef): Int = {
        myMap.get(ar) match {
            case Some(x) => x
            case None => {
                myMap += ar -> new Random().nextInt()
                doSomething(ar)
            }
        }
    }
}

object Main {
    def main(args: Array[String]) {
        case class ExampleClass(x: String);
        val o1 = ExampleClass("test1")
        val o2 = ExampleClass("test1")

        println(o2 == o1) // true
        println(o2 eq o1) // false

                    // I want the following two lines to yield different numbers
                    // and i do not have control over the classes, messing with their
                    // equals implementation is not possible.
        println(Foo.doSomething(o1))
        println(Foo.doSomething(o2))
    }
}

In cases i have instances with the same hash code the "caching" for the random value will return the same value for both instances even those are not same. Which datastructed is used best in this situation?

Clarification/Edit

I know how this works normally, based on the hashCode and equals method. But that is exactly what I want to avoid. I updated my example to make that clearer. :)

回答1:

You can also use IdentityHashMap together with scala.collection.JavaConversions.



回答2:

EDIT: Based on clarifications to the question, you can create your own Map implementation, and override elemEquals().

The original implementation (in HashMap)

protected def elemEquals(key1: A, key2: A): Boolean = (key1 == key2)

Change this to:

protected def elemEquals(key1: A, key2: A): Boolean = (key1 eq key2)

class MyHashMap[A <: AnyRef, B] extends scala.collection.mutable.HashMap[A, B] {
  protected override def elemEquals(key1: A, key2: A): Boolean = (key1 eq key2)
}

Note that to use eq, you need to restrict the key to be an AnyRef, or do a match in the elemEquals() method.

case class Foo(i: Int)
val f1 = new Foo(1)
val f2 = new Foo(1)
val map = new MyHashMap[Foo, String]()
map += (f1 -> "f1")
map += (f2 -> "f2")
map.get(f1) // Some(f1)
map.get(f2) // Some(f2)

-- Original answer

Map works with hashCode() and equals(). Have you implemented equals() correctly in your obejcts? Note that in Scala, == gets translated to a call to equals(). To get the same behaviour of == in Java, use the Scala operator eq

case class Foo(i: Int)
val f1 = new Foo(1)
val f2 = new Foo(1)
f1 == f2 // true
f1.equals(f2) // true
f1 eq f2 // false

val map = new MyHashMap (f1 -> "f1", f2 -> "f2")
map.get(f1) // Some("f2")
map.get(f2) // Some("f2")

Here, the case class implements equals() to be object equivalence, in this case:

f1.i == f1.i

You need to override equals() in your objects to include object equality, i.e something like:

override def equals(o: Any) = { o.asInstanceOf[AnyRef] eq this }

This should still work with the same hashCode().



回答3:

Ah based on comment... You could use a wrapper that overrides equal to have reference semantics.

class EqWrap[T <: AnyRef](val value: T) {
  override def hashCode() = if (value == null) 0 else value.hashCode
  override def equals(a: Any) = a match {
    case ref: EqWrap[_] => ref.value eq value
    case _ => false
  }
}
object EqWrap {
  def apply[T <: AnyRef](t: T) = new EqWrap(t)
}

case class A(i: Int)

val x = A(0)
val y = A(0)

val map = Map[EqWrap[A], Int](EqWrap(x) -> 1)
val xx = map.get(EqWrap(x))
val yy = map.get(EqWrap(y))
//xx: Option[Int] = Some(1)
//yy: Option[Int] = None

Original answer (based on not understanding the question - I have to leave this so that the comment makes sense...)

Map already has this semantic (unless I don't understand your question).

scala> val x = A(0)
x: A = A(0)

scala> val y = A(0)
y: A = A(0)

scala> x == y
res0: Boolean = true // objects are equal

scala> x.hashCode
res1: Int = -2081655426

scala> y.hashCode
res2: Int = -2081655426 // same hash code

scala> x eq y
res3: Boolean = false // not the same object

scala> val map = Map(x -> 1)
map: scala.collection.immutable.Map[A,Int] = Map(A(0) -> 1)

scala> map(y)
res8: Int = 1 // return the mapping based on hash code and equal semantic