![]() |
University of Oxford |
Computing Services |
|
Design of classes |
|
Author: Barry Cornelius Date: January 2001 |
public boolean equals(final Date pDate) { return iYear==pDate.iYear && iMonth==pDate.iMonth && iDay==pDate.iDay; }
Hashtable | contains, containsKey, get, put, remove |
Vector | contains, indexOf |
List | contains, remove, indexOf |
Map | containsKey, containsValue, get, put, remove |
Set | add, contains, remove |
public boolean equals(final Object pObject) { if ( pObject==null || getClass()!=pObject.getClass() ) { return false; } final Date tDate = (Date)pObject; return iYear==tDate.iYear && iMonth==tDate.iMonth && iDay==tDate.iDay; }
Since the equals method appears in a class called Date, you would think that the target of the equals method must be an object of class Date, and so this kind of call of getClass will always return the class Date. However, suppose we derive a class called NamedDate from Date:
public class NamedDate extends Date { private String iName: ... }Suppose that NamedDate does not override equals. If we write:
tFirstNamedDate.equals(tSecondNamedDate)then this will be a call of Date's equals method and both calls of getClass will return the class NamedDate. So, even though this code appears in the class declaration for Date, in some circumstances the first call of getClass will return a value that is a subclass of the class Date.
public boolean equals(final Object pObject) { if ( ! (pObject instanceof Date) ) { return false; } final Date tDate = (Date)pObject; return iYear==tDate.iYear && iMonth==tDate.iMonth && iDay==tDate.iDay; }
public int hashCode(); public boolean equals(Object pObject);
Hashtable | contains, containsKey, get, put, remove |
HashMap | containsKey, containsValue, get, put, remove |
HashSet | add, contains, remove |
public int hashCode() { return 0; }
public int hashCode() { return iMonth; }
final Date tNoelDate = new Date(2000, 12, 25); final int tValue = tNoelDate.hashCode();will assign the value 12 to tValue.
For example, suppose we want to set up a HashSet containing the dates when various composers died:
Bach | 1750-08-28 |
Beethoven | 1827-03-26 |
Cage | 1992-08-12 |
Chopin | 1849-10-17 |
Copland | 1990-12-02 |
Elgar | 1934-02-23 |
Handel | 1759-04-14 |
Mendelssohn | 1847-11-04 |
Purcell | 1695-11-21 |
Sibelius | 1957-09-20 |
Stanford | 1924-03-29 |
Tallis | 1585-11-23 |
Tchaikovsky | 1893-11-06 |
Vaughan-Williams | 1958-08-26 |
Walton | 1983-03-08 |
Suppose we add each of these dates to a HashSet, e.g. for Bach:
final Date tDeathOfBach = new Date(1750, 8, 28); tHashSet.add(tDeathOfBach);
The add method could use Date's hashCode function to store the values in 12 buckets:
Then, when later we ask the collection class whether it has the value 1893-11-06 (the date when Tchaikovsky died), the contains method can call hashCode on this value and, because this produces the value 11, the contains method need only check the values in the 11th bucket. The code of the contains method uses equals on each of these values in turn returning the value true if and only if it finds the value (in this case, the value 1893-11-06).
public int hashCode() { return 0; }
the code of the hashCode method | time taken |
return 0; | 14826 |
return iMonth; | 1235 |
return iYear*10000 + iMonth*100 + iDay; | 36 |
public interface Comparable { public int compareTo(Object pObject); }
public class Date implements Comparable { ... public int compareTo(final Object pObject) { final Date tDate = (Date)pObject; int tResult = iYear - tDate.iYear; if (tResult==0) { tResult = iMonth - tDate.iMonth; if (tResult==0) { tResult = iDay - tDate.iDay; } } return tResult; } }
If a class such as Date fails to implement the Comparable interface, or its implementation of compareTo provides inappropriate code, a client class can still store Date objects in a TreeSet or a TreeMap provided it creates the TreeSet/TreeMap using a constructor that is passed an object that implements the Comparator interface ([7]). This interface requires the object to provide the method:
public int compare(Object pObject1, Object pObject2);This method returns a negative integer, zero, or a positive integer depending on whether the value of pObject1 is less than, equal to, or greater than that of pObject2.
If, bizarrely, we chose to compare dates only on the year field, we could provide:
public class MyDateComparator implements java.util.Comparator { public int compare(final Object pObject1, final Object pObject2) { return ((Date)pObject1).getYear() - ((Date)pObject2).getYear(); } }and then use:
MyDateComparator tMyDateComparator = new MyDateComparator(); final Set tOccurrences = new TreeSet(tMyDateComparator): tOccurrences.add(tDeathOfBach);
Date tBirthDate = new Date(2000, 1, 24); Person tSomePerson = new Person("Joe", tBirthDate);where Person is as follows:
public class Person { private String iName; private Date iDateOfBirth; public Person(final String pName, final Date pDateOfBirth) { iName = pName; iDateOfBirth = pDateOfBirth; // share ...
iDateOfBirth = (Date)pDateOfBirth.clone(); // clone
iDateOfBirth = new Date(pDateOfBirth); // clone
public Object clone();
public class Date implements Cloneable { ... public Object clone() { try { return super.clone(); } catch(final CloneNotSupportedException pCloneNotSupportedException) { throw new InternalError(); } } }
Object's clone method only produces a shallow copy. So, if a class has one or more fields that are of a reference type, we may want to provide a deep copy by cloning these fields. For example, for the Person class, we may want to provide:
public class Person implements Cloneable { ... public Object clone() { try { final Person tPerson = (Person)super.clone(); if (iDateOfBirth!=null) { tPerson.iDateOfBirth = (Date)iDateOfBirth.clone(); } return tPerson; } catch(final CloneNotSupportedException pCloneNotSupportedException) { throw new InternalError(); } } }