Reputation: 2043
package MavenWeb.MavenWeb;
import java.util.HashMap;
import java.util.Map;
public class StringEquality {
public static void main(String[] args) {
Person p1 = new Person("Naveen", 22, 1000);
Person p2 = new Person("Naveen", 21, 2000);
Person p3 = new Person("Naveen", 23, 3000);
if(p1.equals(p2)){
System.out.println("P1 and p2 :" + p1.equals(p2));
} else{
System.out.println("P1 and p2 :" + p1.equals(p2));
}
if(p1.equals(p3)){
System.out.println("P1 and p3 :" + p1.equals(p3));
}
Map<Person, Object> map = new HashMap<Person, Object>();
map.put(p1, p1);
map.put(p2, p2);
System.out.println(map.get(new Person("Naveen", 21, 2000)));
}
}
...
class Person{
public Person(String name, int id, float salary){
this.name = name;
this.id = id;
this.salary = salary;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
String name;
Float salary;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
int id;
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}
if(this == obj){
return true;
}
if(obj instanceof Person){
Person person = (Person)obj;
if((person.getName().equals(name)) && person.getId() == id
&& person.getSalary() == salary){
return true;
}
}
return false;
}
@Override
public int hashCode() {
int hashCode = 1;
hashCode = 31 * hashCode + name.hashCode();
hashCode = 31 * hashCode + id;
hashCode = 31 * hashCode + salary.intValue();
return hashCode;
}
@Override
public String toString() {
return "Name :" + name + ", id : " + id;
}
}
I have write the following program. Little bit confused regarding hashcode implementation,
Upvotes: 2
Views: 2127
Reputation: 3707
you can read this:
http://www.xyzws.com/javafaq/why-always-override-hashcode-if-overriding-equals/20
Why always override hashcode() if overriding equals()?
In Java, every object has access to the equals() method because it is inherited from the Object class. However, this default implementation just simply compares the memory addresses of the objects. You can override the default implementation of the equals() method defined in java.lang.Object. If you override the equals(), you MUST also override hashCode(). Otherwise a violation of the general contract for Object.hashCode will occur, which can have unexpected repercussions when your class is in conjunction with all hash-based collections.
Here is the contract, copied from the java.lang.Object specialization:
public int hashCode()
Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable.
The general contract of hashCode is:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
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.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.
As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)
The default implementation of equals()
method checks to see if the two objects have the same identity. Similarly, the default implementation of the hashCode()
method returns an integer based on the object's identity and is not based on the values of instance (and class) variables of the object. No matter how many times the values of its instance variables (data fields) change, the hash code calculated by the default hashCode implementation does not change during the life of the object.
Consider the following code, we have overridden equals() method to check if two objects are equal based on the values of their instance variables. Two objects may be stored at different memory addresses but may still be equal base on their instance variable.
public class CustomerID {
private long crmID;
private int nameSpace;
public CustomerID(long crmID, int nameSpace) {
super();
this.crmID = crmID;
this.nameSpace = nameSpace;
}
public boolean equals(Object obj) {
//null instanceof Object will always return false
if (!(obj instanceof CustomerID))
return false;
if (obj == this)
return true;
return this.crmID == ((CustomerID) obj).crmID &&
this.nameSpace == ((CustomerID) obj).nameSpace;
}
public static void main(String[] args) {
Map m = new HashMap();
m.put(new CustomerID(2345891234L,0),"Jeff Smith");
System.out.println(m.get(new CustomerID(2345891234L,0)));
}
}
Compile and run the above code, the output result is
null
What is wrong? The two instances of CustomerID are logically equal according to the class's equals method. Because the hashCode()
method is not overridden, these two instances' identities are not in common to the default hashCode implementation. Therefore, the Object.hashCode
returns two seemingly random numbers instead of two equal numbers. Such behavior violates "Equal objects must have equal hash codes" rule defined in the hashCode contract.
Let's provide a simple hashCode() method to fix this problem:
public class CustomerID {
private long crmID;
private int nameSpace;
public CustomerID(long crmID, int nameSpace) {
super();
this.crmID = crmID;
this.nameSpace = nameSpace;
}
public boolean equals(Object obj) {
//null instanceof Object will always return false
if (!(obj instanceof CustomerID))
return false;
if (obj == this)
return true;
return this.crmID == ((CustomerID) obj).crmID &&
this.nameSpace == ((CustomerID) obj).nameSpace;
}
public int hashCode() {
int result = 0;
result = (int)(crmID/12) + nameSpace;
return result;
}
public static void main(String[] args) {
Map m = new HashMap();
m.put(new CustomerID(2345891234L,0),"Jeff Smith");
System.out.println(m.get(new CustomerID(2345891234L,0)));
}
}
Compile and run the above code, the output result is
Jeff Smith
The hashcode distribution for instances of a class should be random. This is exactly what is meant by the third provision of the hashCode contract. Write a correct hashCode method is easy, but to write an effective hashCode method is extremely difficult.
For example, From How to Avoid Traps and Correctly Override Methods From java.lang.Object: If you are unsure how to implement hashCode(), just always return 0 in your implementations. So all of your custom objects will return the same hash code. Yes, it turns hashtable of your objects into one (possibly) long linked-list, but you have implemented hashCode() correctly!
public int hashCode(){
return 0;
}
It's legal because it ensures that equal objects have the same hash code, but it also indicates that every object has the same hash code. So every object will be hashed into the same bucket, and hash tables degenerate to linked lists. The performance is getting worse when it needs to process a large number of objects. How to implement a good hash function is a big topic and we will not cover here
Upvotes: 0
Reputation: 93872
The problem is that your equals implementation is false.
Consider this :
Person p4 = new Person("Naveen", 21, 2000);
System.out.println(p2.equals(p4));
It prints false, or it should print true.
Or map.get(Object key)
need the equals method to check if the key is already store in the map.
More formally, if this map contains a mapping from a key k to a value v such that (key==null ? k==null : key.equals(k))
What's your problem with your equals method ?
In your equals method, you should replace
person.getSalary() == salary
By
1. person.getSalary().equals(salary)
2. person.getSalary().floatValue() == salary.floatValue()
Because getSalary()
returns a Float
object, so ==
will check their references and not their values.
There is no need of a the Float
wrapper class, you should use float salary;
or double salary;
(if you want more precision).
You can use the equals implementations generated by Eclipse :
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (salary == null) {
if (other.salary != null)
return false;
} else if (!salary.equals(other.salary))
return false;
return true;
}
System.out.println(map.get(new Person("Naveen", 21, 2000)));
prints
Name :Naveen, id : 21
Upvotes: 1
Reputation: 692121
You're getting null from the map because the salary is stored as a Float
(and not float
), and you're comparing Float
instances with ==
instead of comparing them with equals()
.
So the equals()
method of Person checks that exactly the same Float
instance is in both Person instances, instead of checking that the value of their salary is equal. You should use float
(or better: double
), unless the salary is nullable (you should also consider using BigDecimal to deal with exact money amounts). But in that case, you'll have to check for null in the Person.equals()
and use equals()
to compare the salaries. The easiest way, in Java 7 or later, is to use Objects.equals()
to compare nullable objects together:
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}
if(this == obj){
return true;
}
if(obj instanceof Person){
Person person = (Person)obj;
return Objects.equals(name, person.name)
&& id == person.id
&& Objects.equals(salary, person.salary);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(name, id, salary);
}
Upvotes: 6
Reputation: 533810
You used the number 31
so it is up to you what makes sense.
The number 31 is used for String as it is an odd prime (which produces more random hash codes) and larger than the number of letters in the alphabet so different letters are less likely to produce the same hash code for different text. With 31 you can have all 5 characters Strings being unique if they use either A-Z or a-z.
However, for longer strings I find that 57 works better for me and for non Strings other, larger prime numbers like 10191 could be better. I like 10191 as 101, 1019 and 10191 are prime numbers.
Upvotes: 3