tig
tig

Reputation: 27830

Dynamically override `equals` and `hashCode` in groovy

If I dynamically override equals and hashCode methods for a class, calling those methods directly calls overridden versions, but using them for set uses non overridden versions. Why is it so and is it still possible to dynamically override those two methods for all usages?

class SuperClass {
  public boolean equals(Object other) {
    println 'non overridden equals called'
    false
  }

  public int hashCode() {
    println 'non overridden hashCode called'
    1
  }
}

SuperClass.metaClass.equals = { Object other ->
  println 'overridden equals called'
  true
}

SuperClass.metaClass.hashCode = { ->
  println 'overridden hashCode called'
  1
}

def a = new SuperClass()
def b = new SuperClass()

println a.hashCode() // overriden hashCode called
println b.hashCode() // overriden hashCode called
println a.equals(b) // overriden equals called

println([a, b].toSet().size()) // non overriden methods called, returns 2 instead of 1

Upvotes: 4

Views: 2295

Answers (1)

tim_yates
tim_yates

Reputation: 171144

Calling toSet() on a List invokes the following code:

    Set<T> answer = new HashSet<T>(self.size());
    answer.addAll(self);
    return answer;

Now HashSet (a Java class) has no concept of the metaClass, and so does not see your overloaded hashCode and equals methods. Therefore you get 2 items in your set.

What you can do is call unique on your list first of all:

println( [a, b].unique().toSet().size() ) 

As this goes through the invoker so knows about the metaClass and should give you a set containing one element.

In practice, I'd avoid changing the hashCode method via the metaClass. As you can see, it's hard to know exactly when it will be handled, and things where it is handled well may not be expecting the hashCode to change from moment to moment.

Upvotes: 3

Related Questions