Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

override equals() method of Pair

Tags:

scala

This question was previously asked in scala-user mailing-list without a confirmed answer.

scala> val T = new Pair(1, 2){
override def equals(obj:Any) = obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1}
           }

T: (Int, Int) = (1,2)

scala> T match {
        case (1, 1) => println("matched")
        case _ => println("not matched")
   }

not matched


scala> (1, 1) match {
              case T => println("matched")
              case _ => println("not matched")
          }

not matched

scala> T == (1, 1)
res15: Boolean = true

I thought a constant(val) pattern matching result depends on the return value of "equals", but the results show that it is not the case, then what is the criteria?

Someone had suggested that case (1, 1) => is an extractor pattern and uses Tuple2.unapply instead. so I tried these:

scala> Pair.unapply(T)
res1: Option[(Int, Int)] = Some((1,2))

scala> Pair.unapply(T).get == (1, 1)
res2: Boolean = true

Can anyone please explain why == get true but I cannot make them match?

like image 718
xiefei Avatar asked Sep 25 '10 09:09

xiefei


People also ask

Can you override equals method?

All classes in Java inherit from the Object class, directly or indirectly (See point 1 of this). The Object class has some basic methods like clone(), toString(), equals(),.. etc. We can override the equals method in our class to check whether two objects have same data or not.

Can we override a equals () and hashCode () method How?

"If two objects are equal using Object class equals method, then the hashcode method should give the same value for these two objects." So, if in our class we override equals() we should override hashcode() method also to follow this rule.

How does the String Object override the equals () method?

The String class overrides the equals method it inherited from the Object class and implemented logic to compare the two String objects character by character. The reason the equals method in the Object class does reference equality is because it does not know how to do anything else.

What is equals () method in Java?

The equals() method compares two strings, and returns true if the strings are equal, and false if not.


3 Answers

The problem with your example is that you only override the equals method of the anonymous class you define your specific tuple to be part of. Let's take a closer look at what you are doing when running the code you have given here.

val p = new Pair(1, 2) {
override def equals(obj:Any) = {
    obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
  }
}

What Scala does here is it creates a new anonymous class which extends Pair and overrides its equals. So this is equivalent to running the following code:

class Foo extends Pair(1,2) {
  override def equals(obj:Any) = {
      obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
    }
}

val p = new Foo

And here is where you can see exactly where the problem lies! The definition of equals is not symmetric. p == (1,1) evaluates to true and (1,1) == p evaluates to false! This is because the former is equivalent to p.equals((1,1)) while the latter is equivalent to (1,1).equals(p). In the example you have given, it doesn't work because the object in the case is compared with the object being matched, and not the other way around. Therefore, as you have indicated, Pair.unapply(p).get == (1, 1) evaluates to true, however (1,1) == Pair.unapply(p).get evaluates to false, and it seems that the latter is the one used when matching.

However, in any case creating an equals that is not symmetric is a really really bad idea as the execution of the code depends on the order you compare objects in. Furthermore, the equals you have defined has a further problem -- it fails with error when you try to compare your p with any Pair that is not of type (Int, Int). This is because, after type erasure (which is how the JVM implements generics), a Pair is no longer parametrised by the types of its constituents. Therefore, (Int, Int) has exactly the same type as (String, String) and thus, the following code will fail with error:

p == ("foo", "bar")

as Scala will try to cast a (String, String) to an (Int, Int).

If you want to implement this functionality, the easiest thing you could do is to use the pimp my library pattern, pimping a Pair. However, you shouldn't call your method equals. Call it something else, like ~=. I have to be going now, however when I come back I could give you the code for it. It's pretty easy. You should look at the implementation of equals in a pair and remove the part that compares the second argument :)

like image 149
Flaviu Cipcigan Avatar answered Sep 25 '22 03:09

Flaviu Cipcigan


I have to say Scala surprised me, again. Some more tests show that the result depends on the running context.

If you run :

object Pair{
    def main(args:Array[String]) {
        val T = new Pair(1, 2){
            override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
        }

        (1, 1) match {
            case T => println("matched")
            case _ => println("not matched")
        }
    }

}

you get: matched

And if you run:

object Pair extends Application {
    val T = new Pair(1, 2){
        override def equals(obj:Any)= obj.isInstanceOf[(Int, Int)] && obj.asInstanceOf[(Int, Int)]._1 == this._1
    }

    (1, 1) match {
        case T => println("matched")
        case _ => println("not matched")
    }
}

you get : not matched

I have always thought that there is no difference between code in main() methods and code in object body which extends Application trait. Really weird.

like image 28
xiefei Avatar answered Sep 22 '22 03:09

xiefei


Tuples are privileged in the pattern matcher. They're not your everyday classes. It's in the spec, section 8.1.7, "Tuple Patterns".

When you say

(1, 1) match { case T ...

Then equals is being called, on (1, 1), which of course says no thanks, not equal.

When you say

T match { case (1, 1) => ...

Then your equals method is ignored because of the tuple pattern, and T._1 is compared to 1 and T._2 to 1, and again it does not match.

like image 42
psp Avatar answered Sep 24 '22 03:09

psp