Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define custom equality in case classes

Tags:

scala

I have a case class Foo defined below. I want to override the behavior of == in that, so that the last element (optBar) is ignored in the comparison. Here is what I have tried and it seems to work.

case class Bar(i:Int)
case class Foo(i:Int, s:String, optBar:Option[Bar]) {
    override def equals(o:Any) = o match {
        case Foo(`i`, `s`, _) => true
        case _ => false
    }
    override def hashCode = i.hashCode*997  ^ s.hashCode * 991
}
val b = Bar(1)
val f1 = Foo(1, "hi", Some(b))
val f2 = Foo(1, "hi", None)
f1 == f2 // true

What I want to know is if the method of creating hashCode is correct. I got it from this link.

like image 704
Jus12 Avatar asked Aug 10 '15 08:08

Jus12


People also ask

Which interfaces can be used to provide custom equality logic?

IEquatable<T> interface by providing a type-specific Equals method. This is where the actual equivalence comparison is performed. For example, you might decide to define equality by comparing only one or two fields in your type.

How does C equal equal work?

In C#, Equals(String, String) is a String method. It is used to determine whether two String objects have the same value or not. Basically, it checks for equality. If both strings have the same value, it returns true otherwise returns false.

Is equal to in C#?

Plain Vanilla Operator == The most common way to compare objects in C# is to use the == operator. For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise.


2 Answers

Your hashCode definition is correct as in that it complies with the equals/hashCode contract. But I think

override def hashCode = (i, s).##

is nicer to read.

To clarify what this does: ## is just a convenience method on scala.Any that calls hashCode, but properly deals with null and some corner cases related to primitives.

val x: String = null
x.## // works fine. returns 0
x.hashCode // throws NullPointerException

So (i, s).## creates a tuple of i and s (which has a well-defined hashCode method) and then returns its hash code. So you don't have to manually write a hash code method involving MurmurHash etc. By the way: this will also properly work if one of the elements of the tuple is null, whereas a hand-written hash method like the one in the question might throw a NPE.

However, in my experience if you want to modify any of the things that a case class provides for you, you don't really want a case class. Also, overriding equality to not take into account some of the data might seem a clever idea at some point, but it can lead to some very confusing behavior.

like image 164
Rüdiger Klaehn Avatar answered Sep 20 '22 18:09

Rüdiger Klaehn


You can also remove the optBar from the case class definition and create a constructor with the three parameters. To avoid having to use the new keyword when you want to use that constructor you can create a companion object.

case class Bar(i:Int)
case class Foo(i:Int, s:String) {
  var optBar: Option[Bar] = None

  def this(i:Int, s:String, optBar:Option[Bar]) {
    this(i, s)
    this.optBar = optBar
  }
}
object Foo {
  def apply(i:Int, s:String, optBar:Option[Bar]) =
    new Foo(i, s, optBar)
}

val b = Bar(1)
val f1 = Foo(1, "hi", Some(b))
val f2 = Foo(1, "hi", None)
f1 == f2 // true
like image 20
Helder Pereira Avatar answered Sep 20 '22 18:09

Helder Pereira