Ruby ==, hash VS Java equals, hashCode

We should take a look what equals() & hashCode() of Java did for us.

We create a simple Java class first.

public class Demo {
  private int i;
  public Demo(int i) { this.i = i; }
}

Let test it with a Set.

Set<Demo> set = new HashSet<Demo>();
set.add(new Demo(1));
set.add(new Demo(1));
System.out.println(set.size()); // Output: 2

It obvioudly not the result we want.
2 Demo objects contain the same variable i, the set should only hold one copy of it.
In other words, the size of the set should be 1.

What’s going on?
Every object of Java inherites the default implementation of equals() fom Object class.

It only tells if 2 objects are equal by comparing their indentities(they’re usually the memory addresses).

Although 2 objects hold the same content, they are not the same by the default implementation of equals().

Fix it!

public class Demo {
  private int i;
  public Demo(int i) { this.i = i; }
  @Override
  public boolean equals(Object o) {
    if (o instanceof Demo) {
      Demo demo = (Demo) o;
      return i == demo.i;
    }
    return false;
  }
}

System.out.println(new Demo(1).equals(new Demo(1))); // Output: true

Then do it again.

Set<Demo> set = new HashSet<Demo>();
set.add(new Demo(1));
set.add(new Demo(1));
System.out.println(set.size()); // Output: 2

The output is still 2. What happened?
It’s because HashSet is based on the hashCode() function to implement.
We have to ensure that any 2 equal objects must get the same hash code value.
In doing so, all HashSet, HashMap, LinkedHashMap… in Java will start to work correctly.

Fix it again!

public class Demo {
  private int i;
  public Demo(int i) { this.i = i; }
  ...
  @Override
  public int hashCode() {
    return 7 * 31 + i;
  }
}

System.out.println(new Demo(1).hashCode() == new Demo(1).hashCode()); // Output: true

See the result.

Set<Demo> set = new HashSet<Demo>();
set.add(new Demo(1));
set.add(new Demo(1));
System.out.println(set.size()); // Output: 1

Finally it works!

This concept can also be used in Ruby, but Ruby proogrammers tend to forget overwriting them.

Let write a simple Ruby class

class Demo
  attr_reader :i
  def initialize(i)
    @i = i
  end
end

We can see the samething happened again.

p Demo.new(1) == Demo.new(1)
=> false
p Demo.new(1).hash == Demo.new(1).hash
=> false

Fix it.

class Demo
  attr_reader :i
  def initialize(i)
    @i = i
  end

  def ==(o)
    if o.kind_of? Demo
      return @i == o.i
    end
    false
  end

  def hash
    7 * 31 + @i
  end
end

It works properly now!

p Demo.new(1) == Demo.new(1)
=> true
p Demo.new(1).hash == Demo.new(1).hash
=> true
Advertisements
This entry was posted in java, ruby. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s