Comparison Java objects
We know that there are two basic ways to compare Java
objects: use the “==” operation and the equals() method.
Assume that we have a class Customer as below:
class
Customer {
private short year;
private String
name;
public
Customer(short year, String name) {
super();
this.year =
year;
this.name =
name;
}
public short
getYear() {
return year;
}
public String
getName() {
return name;
}
//
Add more code here
}
The result of this operation is true when two variables refer
to exactly on object. If we write and run the code as below on the main()
method, we will see that the results are false and true:
public static void
main(String[] args) {
Customer
c1 = new Customer((short) 1985,
"Java85");
Customer
c2 = new Customer((short) 1985,
"Java85");
Customer
c3 = c2;
System.out.println(c1
== c2);// Line 1
System.out.println(c3
== c2);// Line 2
}
Here c1 and c2 variables refer two different objects
although the attributes are the same values. Otherwise, c2 and c2 variables
refer to the second object created, so c3 == c3 returns true.
The equals() method is used when we want to compare
different objects based on their attributes. For example in the real life, if
we see two cubes having the same size and color on each sides, we could say
that they equal.
Each object, when it is created, will inheritance the
equals() method of java.lang.Object. On the default implementation, the
equals() method just compare two objects by the “==” operation. Therefore, if
we change Line 1 and Line 2 on the main() method to become:
System.out.println(c1.equals(c2));//
Line 1
System.out.println(c3.equals(c2));// Line 2
the results are also false and true. In this case, we want that c1.equals(c2)
returns true
because their attributes are the same values. To do it, we need to override the equals()
method. We should compare attributes that we
think that they are important and necessary to compare two objects. For our
example, we will compare year and name attributes of Customer class. Here is
one way to override the equals() method:
@Override
public boolean
equals(Object obj) {
if (obj
== this) {
return true;
}
if (obj instanceof
Customer) {
Customer
c = (Customer) obj;
if
(c.getName().equals(name) && c.getYear() == year)
return true;
}
return false;
}
We add this code after the commend line // Add more code here on the Customer code. After that, if
you run again the main() method, you will have true and true on your output.
Until here, it is enough if you just want to compare values
of objects. You also could use with the contains() method of java.util.List.
List<Customer>
list = new ArrayList<Customer>();
list.add(c1);
System.out.println(list.contains(c1));
System.out.println(list.contains(c2));
Execute the code, the result should be true and true . It is fine.
However, it is not enough to work with java.util.Set and
java.util.Map. We could se this code:
Map<Customer,
Integer> map = new HashMap<Customer,
Integer>();
map.put(c1,
2012);
System.out.println(map.get(c1));
System.out.println(map.get(c2));
Running the code above, we want to see 2012 and 2012
on the result view, but the real result are 2012 and null. We look more on the example
for using java.util.Set:
Set<Customer>
set = new HashSet<Customer>();
set.add(c1);
set.add(c2);
System.out.println(set.size());
The same with the code for java.util.Map, this code you may
want to see the size of that set is 1 because c1 equals to c2, but the real
size is 2.
What is going wrong here? It is because java.util.Map and
java.util.Set use hash code values of objects to compare them. To get exactly what
we want, we must override the hashCode() method for Customer class.
An example for the hashCode() method
We could add the overriding code for the hastCode() method
for Customer class as below:
@Override
public int
hashCode() {
return name.hashCode()
+ year;
}
Then we run the code examples for java.util.Map and
java.util.Set, we will see the results now are 2012 and 2012 for the java.util.Map
example; and 1 for the java.util.Set example.
However, it is not a right way to override the hashCode()
method though it is simple and easy to understand. We should override it on the
way mentioned on the Effective Java ver.2 book of Joshua Bloch.
The way to override hashCode()
Follow that recommendation, we will override it by these steps:
- Declare a constant, for instance, int hashCode = 85;
- For each significant (important and necessary) attribute, we do:
- .If f is a boolean: fc = f ? 1: 0
- .If f is a byte, short, char or int: fc = (int) f
- .If f is a long: fc = (int) (f ^ (f>>>32))
- .If f is a float: fc = Float.floatToIntBits(f)
- .If f is a double: fc = Double.doubleToLongBits(f). And then calculate hashCode for fc as a long type.
- .If f is an array, calculate for each element as a separate field.
- .If f is an object, fc = f.hashCode()
b. Combine fc with hashCode: hashCode = 31 * hashCode + c;
3. Return hashCode.
3. Return hashCode.
Some notes:
- You could ignore insignificant fields.
- The first value hastCode = 85 is arbitrary.
- The number 31 on the step 2b could change, but it is better if it is a prime number. The value 31 is a good choice for performance when 31 * i = (i << 5) – i.
Now we come back to Customer class. We could override the
hashCode() in the effective way:
@Override
public int
hashCode() {
int
hashCode = 85;
hashCode
= 31 * hashCode + year;// year is an int
hashCode
= 31 * hashCode + name.hashCode();//
name is an object (String)
return
hashCode;
}
Now it is fine.
Reference
Effective Java ver.2 - Joshua Bloch
No comments:
Post a Comment