| Home: www.vipan.com | Vipan Singla | e-mail: vipan@vipan.com |
public class TaskToDoImmutable {
private String taskToDo;
private long taskTime;
private int hash;
public TaskToDoImmutable(String taskToDo, long taskTime) {
this.taskToDo = taskToDo;
this.taskTime = taskTime;
// Object cannot change. Calculate hash code at creation time.
this.hash = calculateHashCode();
}
public String getTaskToDo() {
return taskToDo;
}
public long getTaskTime() {
return taskTime;
}
public TaskToDoImmutable changeTaskDescription(String newTaskDescription) {
return new TaskToDoImmutable(newTaskDescription, taskTime);
}
public TaskToDoImmutable changeTaskTime(long newTaskTime) {
return new TaskToDoImmutable(taskToDo, newTaskTime);
}
public boolean equals(Object obj) {
if (obj == null) return false;
if (!this.getClass().equals(obj.getClass())) return false;
TaskToDoImmutable obj2 = (TaskToDoImmutable) obj;
if (
this.taskToDo.equals(obj2.taskToDo) &&
this.taskTime == obj2.taskTime
) {
return true;
}
else return false;
}
public int hashCode() {
return hash;
}
private int calculateHashCode() {
int tmp = 0;
// Method 1-Concatenate the strings
tmp = (taskToDo + Long.toString(taskTime)).hashCode();
// Method 2-Insert a separator before concatenating
tmp = (taskToDo + "|" + Long.toString(taskTime)).hashCode();
// Method 3-Add hash codes
tmp = taskToDo.hashCode() + (new Long(taskTime)).hashCode();
// Method 4-Multiply with prime and add the results
tmp = taskToDo.hashCode()*3 + (new Long(taskTime)).hashCode()*5;
return tmp;
}
}
import java.util.*;
// This class is shown here as a mutable class
public class TaskToDo {
private String taskToDo;
private Date taskTime;
public TaskToDo(String taskToDo, Date taskTime) {
this.taskTime = taskTime;
this.taskToDo = taskToDo;
}
public String getTaskToDo() {
return taskToDo;
}
public Date getTaskTime() {
return taskTime;
}
public void setTaskDescription(String newTaskDescription) {
taskToDo = newTaskDescription;
}
public void setTaskTime(Date newTaskTime) {
taskTime = newTaskTime;
}
public boolean equals(Object obj) {
if (obj == null) return false;
if (!this.getClass().equals(obj.getClass())) return false;
TaskToDo obj2 = (TaskToDo) obj;
if (
this.taskToDo.equals(obj2.taskToDo) &&
this.taskTime.equals(obj2.taskTime)
) {
return true;
}
else return false;
}
public int hashCode() {
return 59878489;
}
}
java.util.Date mydate = new java.util.Date(); as an instance variable, for example), it is still possible to change the internal data field values of those objects but the data fields of the immutable class only contain references to the same objects.
import java.util.*;
public class TaskToDo {
private Date taskTime;
private String taskToDo;
public TaskToDo(String taskToDo, Date taskTime) {
this.taskTime = taskTime;
this.taskToDo = taskToDo;
}
public Date getTaskTime() {
return taskTime;
}
public String getTaskToDo() {
return taskToDo;
}
public TaskToDo changeTaskTime(Date newTaskTime) {
return new TaskToDo(taskToDo, newTaskTime);
}
public TaskToDo changeTaskDescription(String newTaskDescription) {
return new TaskToDo(newTaskDescription, taskTime);
}
}
Here, once initialized through the constructor, the "taskTime" instance variable cannot be reset to some other date; there are no methods provided to be able to do that. However, whoever invoked the constructor from outside this class, will have to make a new "Date" object to pass to this constructor. So, the client can be holding a reference to the "Date" object and can modify the time in the "taskTime"
import java.util.*;
class TestTaskToDo {
public static void main(String[] args) {
String todo = "Find something to do";
Date doItOn = new Date(); // do this task now
TaskToDo task = new TaskToDo(todo, doItOn);
long newTaskTime = System.currentTimeMillis() + 2*60*60*1000;
// Good - returns a new task
task = task.changeTaskTime( new Date(newTaskTime) );
// Bad - modifies internal data of supposedly immutable task's
// instance variable
doItOn.setTime(newTaskTime);
// So, data fields of immutable classes should be immutable too!
}
}
java.util.Collections.unmodifiable...(...); methods.
|
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. This is the strictest implementation of the equals() and hashCode() methods. Two variables being compared are equal if and only if they refer to the exact same object at the exact same memory location. Hash code is not based on the values of instance (and class) variables of the object. Since it is based on the identity of the object, it can not change during the life of the object (until the object is garbage collected) no matter how many times the values of its instance variables (data fields) change.
So, if you serialized an object and deserialized it later, although you logically have the same object, it is going to be deserialized into a completely different memory location and its default equals() method when compared to the original object is going to return false and the default hashCode() method is going to return a completely different value.
class PersonDatabaseRecord {
private String firstName;
private String lastName;
PersonDatabaseRecord(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = firstName;
}
}
This class will construct two different objects even if you pass the same values to the constructor. equals() will return false and hashCode() will return a different integer for the two objects:
class TestPersonDatabaseRecord {
public static void main(String[] args) {
Object person1 = new PersonDatabaseRecord("John", "Doe");
Object person2 = new PersonDatabaseRecord("John", "Doe");
System.out.println("person1.toString() = " + person1.toString());
System.out.println("person2.toString() = " + person2.toString());
System.out.println(
"\nperson1.equals(person2) = " + person1.equals(person2) );
System.out.println("\nperson1.hashCode() = " + person1.hashCode());
System.out.println("person2.hashCode() = " + person2.hashCode());
}
}
Output:person1.toString() = PersonDatabaseRecord@3ac748 person2.toString() = PersonDatabaseRecord@7172ea person1.equals(person2) = false person1.hashCode() = 3852104 person2.hashCode() = 7434986
equals() method, do not use if (incomingObject instanceof MyClassName) which performs a widening conversion. Even if the incoming object is an instance of a subclass of your class, instanceof will return true.
class TestInstanceOf {
public static void main (String[] args) {
Exception e = new Exception();
RuntimeException re = new RuntimeException();
System.out.println("Is object \"re\" instance of class Exception?:" +
(re instanceof Exception));
}
}
Output:Is re instance of Exception?:true
The reason you subclassed is (almost always) because your subclass is different from its superclass. May be the difference is in behavior only and not in state. But who wants to worry about that? You are better off comparing the actual classes of the two objects, to be sure. So, use this.getClass().equals(incoming.getClass())
equals() method, do not call if (super.equals(incoming) ) anywhere as a shortcut. The superclass may very well have inherited the java.lang.Object.equals() method which will return false if the two objects reside at different memory addresses in the running JVM. Insist on knowing the state (data fields) of your object, including that inherited from its superclasses, compare it with the state of the incoming object (after comparing the class names) and return true only if the two states are equal according to the criteria you establish. This can get cumbersome for deep class hierarchies (more than 2 or 3 subclasses of java.lang.Object?!).
hashCode() "method is supported for the benefit of hashtables such as those provided by java.util.Hashtable".
java.lang.Object.equals() method, you must override java.lang.Object.hashCode() to make sure that the objects which are equal return the same hash code. It is very hard to override equals() and hashCode() methods in a mutable class and be sure that they will work correctly all the time.
java.util.Date.hashCode() for an example.
System.identityHashCode(yourObject);
hashCode() correctly!
public int hashCode(){
return 0;
}
public int hashCode() {
return 3048756;
}
Keep in mind that two equal objects must return the same integer. This is not a problem if the same class constructs the two equal objects. Both objects will have the same hashCode() method and hence, return the same integer. You may have a problem if you are trying to be smarter and force two objects from two different classes as being equal. Then, you must ensure that the hashCode() method of both classes returns the same integer.
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]where
s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.)
Please don't try to rely on each data field's toString() method too much to get the strings. The toString() method may return different values at different times or for different but equal objects. For example, someone may have overridden toString() and inserted code to embed current date and time in the returned string. Instead, make your own strings which always return the same string for the same value of a data field.
As a small technical matter, two different sets of strings can concatenate to the same string:
a + b + c = abc ab + c = abcAgain, this is not a problem. Two different objects are allowed to return the same hash code. Java will be able to resolve between the two objects using their equals() method. But, if you would like to squeeze out that extra bit of efficiency from hash tables, you may want to decide on a separator character and insert it between consecutive data fields:
a + , + b + , + c = a,b,c ab + , + c = ab,c
This still doesn't guarantee unique hash codes. Two different strings can return the same hash code:
// x + 31x = x(31 + 1) = x + 31 + 31(x-1)
public class Identical {
public static void main(String[] args) {
String s1 = new String("BB");
String s2 = new String("Aa");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
Output:
2112 2112
But we are not looking for unique hash codes, just close to being unique will suffice for most purposes.
java.lang.Long.hashCode() documentation)
Integer.MAX_VALUE (2 billion-ish). Integer addition doesn't throw an exception. It simply starts over from the lowest negative number, just like an odometer. All you are worried about is returning the same integer for equal objects. You don't really care what that integer is (nor do you care if the returned integer is unique).
public class TestIntRollover {
public static void main (String[] args) {
int i = Integer.MAX_VALUE;
System.out.println("Maximum i = " + i);
System.out.println("Maximum i + 1 = " + (i+1));
System.out.println("Maximum i + 2 = " + (i+2));
System.out.println("Maximum i + 3 = " + (i+3));
System.out.println("Maximum i + 10 = " + (i+10));
System.out.println("Maximum i + 100 = " + (i+100));
System.out.println("Maximum i + 1000 = " + (i+1000));
System.out.println("Maximum i + 10000 = " + (i+10000));
}
}
Output:
Maximum i = 2147483647 Maximum i + 1 = -2147483648 Maximum i + 2 = -2147483647 Maximum i + 3 = -2147483646 Maximum i + 10 = -2147483639 Maximum i + 100 = -2147483549 Maximum i + 1000 = -2147482649 Maximum i + 10000 = -2147473649
myHashtable.put(objectA, someValue), it first goes to the location indicated by objectA's hash code. If there is already an object there - may be because objectB has been stored there as a key previously, Java checks to see if objectA.equals(objectB). If yes, it replaces the value of key objectB with "soemValue". If no, Java puts objectA and someValue at a different location and creates a link from objectB to objectA. Next time Java needs to get the value of a key, say objectA, from the hash table, it goes to the location indicated by the hash code of the key object. Finding two objects there, it checks if the first object (objectB) equals the object in hand (objectA). If yes, it returns the value for the first object. If no, it goes down the link and checks if the second object (objectA) equals the object in hand (objectA). If yes, it returns the value of the second object. And so on.
"As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur."
Later, you change the values of the fields of the instantiated object (because you can, the class is mutable). Now, when you ask the hashtable to retrieve this object as a key, the hashtable will ask the object for its hash code which will now be different (field values changed). Hashtable will go to the location indicated by the hash code and will not find any object which equals your object. (The object was originally stored at a completely different location as indicated by the hash code from the old data field values). So, the hashtable will return null. The object as a key is now hopelessly lost in the hashtable. You can still get to it if you get all the keys from the hash table and iterate over them, calling objectInHand.equals(eachKey), but that's a manual process that you will need to implement. You cannot say hashtable.get(objectInHand) and hope to get back the object.
java.util.Date is a mutable class which overrides the hashCode() method to return an integer based on the values of its data fields. After a Date object has been instantiated, it is possible to set its data fields to a different value using dateObject.setTime(timeInMillis). If you put a Date object as a key in a hash table and then set some other time on it, the hash table quickly loses track of it.
import java.util.*;
public class TestMutable {
public static void main (String[] args) {
Hashtable map = new Hashtable();
long time = System.currentTimeMillis();
Date dt = new Date(time);
Date dt2 = new Date(time);
System.out.println("dt.toString() = " + dt.toString());
System.out.println("dt2.toString() = " + dt2.toString());
System.out.println("Is dt2.equals(dt)? = " + dt2.equals(dt));
map.put(dt, "blah");
System.out.println("map.get(dt) = " + map.get(dt) );
System.out.println("map.get(dt2) = " + map.get(dt2) );
// Change dt by adding a day to its time
long newTime = time + 24*60*60*1000;
dt.setTime(newTime);
System.out.println("\nAfter dt.setTime(newTime):");
System.out.println("dt.toString() = " + dt.toString());
System.out.println("dt2.toString() = " + dt2.toString());
System.out.println("Is dt2.equals(dt)? = " + dt2.equals(dt));
System.out.println("map.get(dt) = " + map.get(dt) );
System.out.println("map.get(dt2) = " + map.get(dt2) );
System.out.println("\nmap = " + map.toString() );
}
}
Output:
dt.toString() = Thu Jan 03 12:46:33 EST 2002
dt2.toString() = Thu Jan 03 12:46:33 EST 2002
Is dt2.equals(dt)? = true
map.get(dt) = blah
map.get(dt2) = blah
After dt.setTime(newTime):
dt.toString() = Fri Jan 04 12:46:33 EST 2002
dt2.toString() = Thu Jan 03 12:46:33 EST 2002
Is dt2.equals(dt)? = false
map.get(dt) = null
map.get(dt2) = null
map = {Fri Jan 04 12:46:33 EST 2002=blah}
So, after mutating the object which stored as a key in a hashtable, you cannot access the key using its old time or the new time. You cannot even construct a new key initialized with the new time and hope to get the value from the hashtable.
But, as you can see from map.toString(), the hash table contains a key whose value is equal to the new time. If the hash table were to always search for keys using the equals() method, it would have found the modified Date object. But no, hash table first gets the hash code, goes to that location in the hash table, and then starts to apply the equals() method on each object linked to that location. If a key's hash code changes, the hash table search will land up at a totally different location and it will apply the equals() method to the objects stored there.
Had the Date object returned the same hash code no matter what its data field value is, this problem won't have arisen. Let's say it returned 0 all the time. Hash table will always go to the 0 location to store and retrieve a Date object. It will then be able to always find the correct Date object using a linear search (applying the equals() method to each object stored at that location). It is slow (there can be numerous Date objects) but it is correct.
public class TestPrimes {
static public java.util.BitSet method1(int cap) {
java.util.BitSet bits = new java.util.BitSet(cap);
for (int i=1; i<=cap; i++) bits.set(i);
for (int i=2; i<=((int)Math.sqrt(cap))+1; i++)
if (bits.get(i)) for (int j=i+i; j<=cap; j+=i) bits.clear(j);
return bits;
}
static public int[] method2(int howmany) {
int[] primes = new int[howmany];
primes[0] = 1;
primes[1] = 2;
int count = 2;
outer:
for (int i=3; count<primes.length; i+=2) {
int limit = ((int) Math.sqrt(i))+1;
for (int j=1; j<count && primes[j]<=limit; j++)
if ( (i%primes[j]) == 0) continue outer;
primes[count++] = i;
}
return primes;
}
public static void main (String[] args) {
// Method 1
int cap = 101;
java.util.BitSet bits = method1(cap);
for (int i=0; i<=cap; i++) if (bits.get(i)) System.out.print(i+",");
System.out.println();
int lastPrime = Integer.MAX_VALUE;
if (bits.length() != 0) bits.clear(lastPrime = bits.length()-1);
System.out.println("Last prime in Bitset was = " + lastPrime);
// Method 2
int howmany = 27;
int[] primes = method2(howmany);
for (int i=0; i<primes.length; i++) System.out.print(primes[i]+",");
System.out.println();
}
}
Output:
1,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101, Last prime in Bitset was = 101 1,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,