0

Consider the following code:

import scala.collection.mutable

case class A(b: Int) {
  override def equals(obj: Any): Boolean = {
    println("called")
    obj match {
      case o: A => b == o.b
      case _ => false
    }
  }
}

val set = mutable.Set.empty[A]
val a1 = A(1)
set.add(a1)
println(set.contains(A(1)))
println(set.contains(A(2)))

Why the second call of set.contains doesn't print out "called"? Can someone explain how set identify two objects being equal?

Yanqi Huang
  • 404
  • 5
  • 12
  • 1
    A mutable `Set` is backed by a `HashSet` so it first calls the `hashcode` to identify some bucket and then uses `equals` to compare across the elements of the bucket. This why `hashcode` and `equals` MUST always be consistent, if not you open yourself the door to bugs like this one. - BTW, in practice, never override `equals` of a **case class**, if you want custom equiuality use a normal class instead. – Luis Miguel Mejía Suárez Jan 19 '21 at 22:11
  • @LuisMiguelMejíaSuárez Wonderful! Can you explain what's the original implementation of `hashCode` in Scala and how I could override it if I only want to look at a subset of attributes of a class to determine wether two instances are equal. – Yanqi Huang Jan 19 '21 at 22:18
  • Search for _"Effective Java hashcode and equals"_ - And please, use a normal class unless you want to be heated for future collaborators of your code. Also, I would recommend ticking on a different design, for example moving fields that are not part of equality to a second argument list or having two case classes. – Luis Miguel Mejía Suárez Jan 19 '21 at 22:20
  • @LuisMiguelMejíaSuárez Wonderful! That solves my problems completely. Would you mind writing a brief answer so that I could accept it and close this question? Thanks! – Yanqi Huang Jan 19 '21 at 22:25

1 Answers1

2

As @LuisMiguelMejíaSuárez mentioned in the comments, A mutable Set is backed by a HashSet. It is well elaborated at What issues should be considered when overriding equals and hashCode in Java?

Whenever a.equals(b), then a.hashCode() must be same as b.hashCode().

This is because hashCode is a prior check, which when fails equals will definitely fail as well.

From What code is generated for an equals/hashCode method of a case class? we can conclude that for case classes hashCode and equals are overridden for case classes, using productPrefix which means the first set of elements in the case class. For example, if we have:

case class c(a: A)(b: B)

The hashCode and equals will be calculated only based on a. In your case, the hasCode method is calculated based on gender, which is different for Person(1) and Person(2). Therefore there is no need to check equals, which suppose to fail as well.

A tip from What issues should be considered when overriding equals and hashCode in Java?

In practice:

If you override one, then you should override the other.

Use the same set of fields that you use to compute equals() to compute hashCode().

Tomer Shetah
  • 7,646
  • 6
  • 20
  • 32