Why is equals() not called while adding to HashSet and hashCode matches? - java

When I run this code why only hashCode() is called not equals method while my hashCode() implementation generate same hashCode for both entries to HashSet?
import java.util.HashSet;
public class Test1 {
public static void main(String[] args) {
Student st=new Student(89);
HashSet st1=new HashSet();
st1.add(st);
st1.add(st);
System.out.println("Ho size="+st1.size());
}
}
class Student{
private int name;
private int ID;
public Student(int iD) {
super();
this.ID = iD;
}
#Override
public int hashCode() {
System.out.println("Hello-hashcode");
return ID;
}
#Override
public boolean equals(Object obj) {
System.out.println("Hello-equals");
if(obj instanceof Student){
if(this.ID==((Student)obj).ID){
return true;
}
else{
return false;
}
}
return false;
}
}
The output for this is:
Hello-hashcode
Hello-hashcode
Ho size=1

The hash set checks reference equality first, and if that passes, it skips the .equals call. This is an optimization and works because the contract of equals specifies that if a == b then a.equals(b).
I attached the source code below, with this check highlighted.
If you instead add two equal elements that are not the same reference, you get the effect you were expecting:
HashSet st1=new HashSet();
st1.add(new Student(89));
st1.add(new Student(89));
System.out.println("Ho size="+st1.size());
results in
$ java Test1
Hello-hashcode
Hello-hashcode
Hello-equals
Ho size=1
Here's the source code from OpenJDK 7, with equality optimization indicated (from HashMap, the underlying implementation of HashSet):
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// v-- HERE
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}

A HashSet uses a HashMap as its backing mechanism for the set. Normally, we would expect that hashCode and equals would be called to ensure that there are no duplicates. However, the put method (which calls a private putVal method to do the actual operation) makes an optimization in the source code:
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
If the hash codes match, it first checks to see if the keys are the same before calling equals. You are passing the same Student object, so they are already ==, so the || operator short-circuits, and equals is never called.
If you passed in a different Student object but with the same ID, then == would return false and equals would be called.

Looking through the source code of HashSet it is using HashMap for all of its operations and the add method performs put(element, SOME_CONSTANT_OBJECT). Here is the source code for the put method for JDK 1.6.0_17 :
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
as you can see it performs a == comparison before using the equals method. Since you are adding the same instance of an object twice the == returns true and the equals method is never called.

Equals is always called after the hashCode method in a java hashed
collection while adding and removing elements. The reason being, if
there is an element already at the specified bucket, then JVM checks
whether it is the same element which it is trying to put.
hashcode() and equals() method

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

Related

What are hashcode and equals in entry class in hashmap

I can see static Entry class in Hashmap in java have Equals and Hashcode methods.What is the purpose of these methods.put,get methods we use object hashcode and equals methods....
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
Map.Entry is a representation of a key-value pair, in a map. All of this methods can be used for comparing key-value pairs, not for comparing key-objects.
Entryes are objects too, it's entirely possible to compare them for equality or to use them as keys in a map.
In addition, they are sometimes used as a poor man's Pair class: Didn't Java once have a Pair class?, https://www.baeldung.com/java-pairs, https://www.techiedelight.com/five-alternatives-pair-class-java/ and for this use you really want these implementations of equals and hashCode.

Implementing equals and hashcode for a BST

This question is sort of a follow up to Implementing hashCode for a BST. My question was poorly thought through and so I got an answer that I am not sure how to use.
I need to implement equals for a BST: so that iff two BSTs are equal in structure and content, then equals returns true. As such, I imagine I also need to implement the hashCode function. I got the answer for the hashCode function such that the trees are equal in structure and content.
#Override
puclic int hashCode(){
int h = Objects.hashCode(data);//data is int
int child=0;
if(null != left)
child =left.hashCode();
if(null != right)
child+= right.hashCode();
if(0<child) h= h*31+child;
return h;
}
But then how do I implement the equals function? Will the following work iff the trees are equal in both structure and content?
#Override
public boolean equals(Node otherRoot){
return root.hashCode() == otherRoot.hashCode();
}
Might there be circumstances where I can false positives?
Or should my hashCode be
#Override
public int hashCode(){
int h = contents.hashCode();
h = h * 31 + Objects.hashCode(leftChild);
h = h * 31 + Objects.hashCode(rightChild);
return h;
}
and in this latter case, would my equals avoid false positives?
Will the following work iff the trees are equal in both structure and content? root.hashCode() == otherRoot.hashCode()
No, it would not work, because hash code equality is a one-way street: when objects are equal, hash codes must be equal. However, when objects are not equal, hash codes may or may not be equal. This makes sense once you apply a pigeonhole principle: the number of possible hash codes is about 4B, while the number of possible BSTs is virtually infinite.
You can build a comparison in the same way that you built the hash code - i.e. recursively:
Check if the values at the nodes being compared are equal to each other. If the values are different, return false
Check if both nodes have a left subtree. If one of them has a left subtree and the other one does not, return false
Check if both nodes have a right subtree. If one of them has a right subtree and the other one does not, return false
Apply equals recursively to left subtrees. If the result is false, return false
Apply equals recursively to right subtrees. If the result is false, return false
Return true
Not sure what Objects is, but your last hashCode() example needs to handle null, I would think something like:
#Override
public int hashCode() {
int h = contents.hashCode();
if (leftChild != null) h = h* 31 + leftChild.hashCode();
if (rightChild != null) h = h * 31 + rightChild.hashCode();
return h;
}
I can see overflowing h if the tree is deep enough, with all the h * 31.
The contract for hashCode does not guarantee equality, so you probably need to call equals all the way down the tree to make sure everything balances out.
I haven't tested this exactly but here's somewhere to start
public boolean equals(Object o) {
// exact same object
if(this === o) {
return true;
}
if(!o instanceof Node) {
return false
}
Node otherTree = (Node) o;
boolean selfHasLeft = this.left == null,
selfHasRight = this.right == null,
otherHasLeft = otherTree.left == null,
otherHasRight = otherTree.right == null;
// this tree must have the same children as the other tree
if(selfHasLeft != otherHasLeft || selfHasRight != otherHasRight) {
return false;
}
// must have same value
if(this.value != other.value) {
return false;
}
// if they have no children then now they should be the same tree
// otherwise, check that their children are the same
if(!selfHasLeft && !selfHasRight) {
return true;
} else if(selfHasLeft && !selfHasRight) {
return this.left.equals(otherTree.left);
} else if(selfHasRight && !selfHasLeft) {
return this.right.equals(otherTree.right);
} else {
return this.left.equals(otherTree.left) && this.right.equals(otherTree.right);
}
}
Your second hashCode implementation looks good to me, but you can never avoid hashcode collisions when the number of possible objects are greater than the range of an int - which is the case here so you should not use the hashcode in equals.
What you should do is something like this (assuming the class name is BST):
public boolean equals(Object other) {
if(this == other) {
return true;
}
if(!(other instanceof BST)) {
// If other is null we will end up here
return false;
}
BST bst = (BST) other;
// Check equality of the left child
if(left != null) {
if(!left.equals(other.left)) {
// Left childs aren't equal
return false;
}
} else if (other.left != null) {
// this.left is null but other.left isn't
return false;
}
// Check equality of the right child
if(right != null) {
if(!right.equals(other.right)) {
// Right childs aren't equal
return false;
}
} else if (other.right != null) {
// this.right is null but other.right isn't
return false;
}
// Both left and right childs are equal
return true;
}

HashMap with incorrect equals and HashCode implementation

According to what I have read,
to use an object as the key to a hashMap, it has to provide a correct
override and implementation of the equals and hashCode
method. HashMap get(Key k) method calls hashCode method on the key
object and applies returned hashValue to its own static hash
function to find a bucket location(backing array) where keys and
values are stored in form of a nested class called Entry (Map.Entry).
HashMap's internal hash Method defends against poor quality hash
functions.
To test these contracts, I have written a bean class with incorrect but legal implementations of the equals and hashCode method.
The class:
public class HashVO {
private String studentName;
private int age;
private boolean isAdult;
public HashVO(String studentName, int age, boolean isAdult) {
super();
this.studentName = studentName;
this.age = age;
this.isAdult = isAdult;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isAdult() {
return isAdult;
}
public void setAdult(boolean isAdult) {
this.isAdult = isAdult;
}
#Override
public String toString() {
return studentName + " : " + age + " : " + isAdult;
}
#Override
public boolean equals(Object obj) {
return false;
}
#Override
public int hashCode() {
return 31;
}
}
In this case, the hash method of the HashMap,
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
should also return same value everytime because the hashcode always returns 31. So if objects of class HashVO are used as key of a hashMap, the get method should not work, as it should go to the same bucket to retrieve the objects and the equals method always returns false so it will not be able to able to find a match for the key object.
But when I am using this method,
public static void main(String[] args) {
HashMap<HashVO, String> voMap = new HashMap<HashVO, String>();
HashVO vo = new HashVO("Item1", 25, true);
HashVO vo1 = new HashVO("Item2", 12, false);
HashVO vo2 = new HashVO("Item3", 1, false);
voMap.put(vo, "Item");
voMap.put(vo1, "Item1");
voMap.put(vo2, "Item2");
System.out.println(voMap.get(vo));
System.out.println(voMap.get(vo1));
System.out.println(voMap.get(vo2));
}
the output is correct, and showing
Item
Item1
Item2
I want to understand why this correct output is coming even as the Equals and HashCode method implementation is incorrect.
HashMap has a little trick where it compares object references before using equals. Since you're using the same object references for adding the elements and for retrieving them, HashMap will return them correctly.
See Java 7 source here (Java 8 did a pretty big revamp of HashMap but it does something similar)
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
// HERE. Uses == with the key
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
Note that this isn't part of the docs, so don't depend on it.
The HashMap works like this:
1) Index of table cell in which (Key,Value) will save calculates as key.hashCode();
2) Keys in HashMap compare by equals() or by reference comparing.
So, in your situation all pairs of (K,V) will store in one cell of HashMap table as LinkedList.
And you can get them from Map because references for keys will equals

What is Object k?

public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
I am trying to understand the HashMap implemintation. I understood everything except this line - Object k;
Please, explain how this Object k appears??
In that implementation of a HashMap, the data structure was backed by an array of linked lists of entries. These entries have a key and a value.
That variable k is used to store the key for each entry while iterating over the linked list bucket. If it's found to be equal (reference and then value equality) to the key with which you're trying to insert a value, then that value replaces the old.

what happend when the Map's containsKey(E e) is called in Java

i've checked the source code :
public boolean containsKey(Object key) {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}
public boolean equals(Object obj) {
return (this == obj);
}
from the source code , it shows only the "equal()" method has been called , so if i wanna put an custom Object in a map , i could only have to override the "equal()" method. so i did an experiment , the result is negative ... i have to override both "equals()" and "hashCode()" . so my question is :
why both method (equals() ,hashCode()) has to be override.
does the "==" operation internally calls the "hashCode()" method?
This is AbstractMap's implementation. HashMap, which overrides it, implements it as follows:
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
As you can see, this depends on the hashCode().
Other map types may indeed not depend on this method being overriden.
From Object.equals API: it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes. You can find more detailed explanation in "Effective Java" Item 9: Always override hashCode when you override equals.
No it does not, it is simply pointers comparison, just like comparing two ints

Categories