Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map of with object references as keys?

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

like image 384
Malax Avatar asked Oct 16 '25 19:10

Malax


2 Answers

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

like image 121
Matthew Farwell Avatar answered Oct 19 '25 11:10

Matthew Farwell


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

like image 30
Alexey Romanov Avatar answered Oct 19 '25 11:10

Alexey Romanov