带对象引用的映射为键?

时间:2011-11-10 13:59:33

标签: scala data-structures map

我有一个对象,其中包含有关特定实例的商店信息。为此,我想使用Map,但因为键不是引用(它们不是,对吧?),而是由getHashCode方法提供的哈希。为了更好地理解:

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

如果我的实例具有相同的哈希码,则随机值的“缓存”将为两个实例返回相同的值,即使这些实例不相同。在这种情况下哪种数据最佳使用?

澄清/修改

我知道这是如何正常工作的,基于hashCodeequals方法。但这正是我想要避免的。我更新了我的例子以使其更清晰。 :)

3 个答案:

答案 0 :(得分:4)

编辑:根据对问题的澄清,您可以创建自己的Map实现,并覆盖elemEquals()。

原始实现(在HashMap中)

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

将其更改为:

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

请注意,要使用eq,您需要将键限制为AnyRef,或者在elemEquals()方法中进行匹配。

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)

- 原始答案

Map使用hashCode()和equals()。你在obejcts中正确实现了equals()吗?请注意,在Scala中,==会转换为对equals()的调用。要在Java中获得==的相同行为,请使用Scala运算符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")

这里,case类将equals()实现为对象等价,在这种情况下:

f1.i == f1.i

您需要在对象中覆盖equals()以包含对象相等性,即:

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

这应该仍然适用于相同的hashCode()。

答案 1 :(得分:4)

您也可以将IdentityHashMapscala.collection.JavaConversions一起使用。

答案 2 :(得分:1)

啊基于评论......您可以使用覆盖等于具有引用语义的包装。

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

原始答案(基于不理解问题 - 我必须留下这个以便评论有意义......)

地图已经有了这种语义(除非我不理解你的问题)。

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