«^»
4.10. Stage E: the final version of the Date class

4.10.1. Stage E1: the text of the final version of the Date class

The final version of the class declaration for Date is given below. It contains a number of new features.

0399: // A class for representing values that are dates.              // Date.java
0400: import java.util. StringTokenizer;
0401: public class Date 
0402: {
0403:    private int iYear;
0404:    private int iMonth;
0405:    private int iDay;
0406:    public Date()
0407:    {
0408:       this(0, 0, 0);
0409:    }
0410:    public Date(final Date pDate)
0411:    {
0412:       this(pDate.iYear, pDate.iMonth, pDate.iDay);
0413:    }
0414:    public Date(final int pYear, final int pMonth, final int pDay) 
0415:    {
0416:       iYear = pYear;  iMonth = pMonth;  iDay = pDay;
0417:    }
0418:    public Date(final String pDateString)
0419:    {
0420:       try
0421:       {
0422:          final StringTokenizer tTokens = new StringTokenizer(pDateString, "-");
0423:          final String tYearString = tTokens.nextToken();
0424:          iYear = Integer.parseInt(tYearString);
0425:          final String tMonthString = tTokens.nextToken();
0426:          iMonth = Integer.parseInt(tMonthString);
0427:          final String tDayString = tTokens.nextToken();
0428:          iDay = Integer.parseInt(tDayString);
0429:       }
0430:       catch(Exception pException)
0431:       {
0432:          iYear = 0;  iMonth = 0;  iDay = 0;
0433:       }
0434:    }
0435:    public int getYear()
0436:    {
0437:       return iYear;
0438:    }
0439:    public int getMonth()
0440:    {
0441:       return iMonth;
0442:    }
0443:    public int getDay()
0444:    {
0445:       return iDay;
0446:    }
0447:    public void setYear(final int pYear)
0448:    {
0449:       iYear = pYear;
0450:    }
0451:    public void setMonth(final int pMonth)
0452:    {
0453:       iMonth = pMonth;
0454:    }
0455:    public void setDay(final int pDay)
0456:    {
0457:       iDay = pDay;
0458:    }
0459:    public boolean equals(final Object pObject)
0460:    {
0461:       if ( ! (pObject instanceof Date) )
0462:       {
0463:          return false;
0464:       }
0465:       return iYear==((Date)pObject).iYear &&
0466:              iMonth==((Date)pObject).iMonth &&
0467:              iDay==((Date)pObject).iDay;
0468:    }
0469:    public int hashCode()
0470:    {
0471:       return iYear*416 + iMonth*32 + iDay;
0472:    }
0473:    public String toString()
0474:    {
0475:       return iYear + "-" + iMonth/10 + iMonth%10 + "-" + iDay/10 + iDay%10;
0476:    }
0477: }

4.10.2. Stage E2: providing other constructors

Earlier, we saw that a class declaration can have several method declarations each having the same name provided the types of the parameters of each declaration are different (method overloading). In the same way, a class declaration can provide several constructors so long as the types of the parameters of each constructor are different.

Although we have a means of outputting the value of a date object, we currently have no means of reading a textual representation of a date from the keyboard or from a file. Obviously, we could use readLine to read a textual representation of a date and store it in a string. What we then need is a way of parsing the string and forming an appropriate Date object. The above class declaration contains a constructor that can be used to initialize a new Date object from a string (as well as one for initializing a date from three integers). It could be used as follows:

Date tTodaysDate = new Date("1999-09-26");

The above class declaration for Date also contains the constructor:

public Date()
{
   this(0, 0, 0);
}
This constructor is one which has no parameters, and so it would get used for the following declaration:
Date tDate = new Date();
The body of this constructor contains some magic: the this means ‘use the constructor that you will find elsewhere in this class declaration that matches the arguments following the this’. So since the 0, 0, 0 are three ints then the this(0, 0, 0) leads to using the constructor that has three ints as parameters. If you use this in this way, the this statement must appear as the first statement of the constructor.

By this means, you can provide a constructor that has no arguments in order to generate an object with default values (that you can choose).

Earlier it was mentioned that the classes of the Core APIs use two different ways of producing a copy of an object:

Getting the code of a clone method completely right is difficult: instead, the above class declaration provides Date with a constructor that can be used for cloning.

Here is an example of how this constructor can be used. If we first do something like:

final Date tNoelDate = new Date(1999, 12, 25);
we can later pass this reference variable as an argument to the new constructor:
final Date tHappyDate = new Date(tNoelDate);
We finish up with two reference variables pointing to two different objects that have the same value.

4.10.3. Stage E3: defining a method called equals

If a client uses Date variables, then using == on these variables only determines whether they are pointing to the same object. However, if a class declares an appropriate method called equals, then a client can determine whether the dates are the same. So, when you declare a class, it is important to declare a method called equals. This is done in the class declaration for Date that is given above.

If we declare a class and fail to declare a method called equals, then equals can still be applied to an object of the class because the class java.lang.Object has a method called equals. However, Object's equals will just tell you whether the target and the argument point to the same objects (i.e., it does the same as ==): it will not compare the values of the two objects.

The above declaration of equals has the following header:

public boolean equals(final Object pObject);
rather than:
public boolean equals(final Date pDate);
So why would you want to work with a parameter of type Object? Well, one important reason will occur when we look at forming collections of data: for example, we may be wanting to represent a collection of dates, a collection of strings, a collection of points, and so on. Java has a number of useful classes that can be used to manipulate collections. So as to make these classes generally useful the methods of these classes are written in terms of the type Object. For example:
public boolean add(Object pObject);
public    void add(int pIndex, Object pObject);
public boolean contains(Object pObject);
public  Object get(int pIndex);
public boolean remove(Object pObject);
public  Object remove(int pIndex);
are methods that can be used to perform operations on one kind of collection (a list). These collection classes are discussed in ITS Guide 108 Advanced Java.

These collection classes are wonderful because they allow us to create dynamically growing collections of objects. But the person who wrote the code of the methods of these collection classes was not in a position to know what sort of objects you would be storing in a collection. When you call a method like contains (that finds out whether an object is in the collection), behind the scenes contains will call a method with the header:

public boolean equals(Object pObject);
Now, if you are storing Dates in the collection, and if in the class Date you have declared equals with a parameter of type Date:
public boolean equals(final Date pDate);
then this equals will not be called (because the type of the parameter is different). Instead, the method called equals from the class Object will be called: as explained earlier this returns true if and only if the target and the argument point to the same object (and not if the two objects have the same values). This would be an inappropriate method to be used by contains. So instead we declare equals with a parameter of type Object.

Such a method declaration overrides the public boolean equals(Object pObject) that is declared in the class java.lang.Object.

4.10.4. Stage E4: adding hashCode (to help with using collections)

You can imagine that when checking whether a collection contains a particular object it can be quite time-consuming to use equals on each of the objects of the collection in turn. Instead, clever techniques are used to reduce the number of items of the collection that need to be checked. Some of these techniques require there to be an integer (called a hashcode) associated with each of the possible values that can be stored in the collection. And to speed up the execution of methods like contains, the objects in the collection are arranged so that the ones that have the same hashcode are kept together.

In order to support this, the class java.lang.Object has a method called hashCode:

public int hashCode();

The integer that is returned is one that is unique for that object.

However, this method is inappropriate when a class declares a method called equals. What we need to do is to provide our own version of hashCode that overrides the one of java.lang.Object.

The WWW page that documents hashCode says: ‘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(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.’

The above class declaration gives one possibility for hashCode. However, there are many other possibilities. Instead, you could get hashCode to return the value of iYear*10000 + iMonth*100 + iDay. Although it would still work if you got it to return the value of iYear + iMonth + iDay, or the value iDay, or the value 42, these will lead to poorer performance as the same integer value is being produced for unequal objects.

4.10.5. Stage E5: using the new version of the Date class

The following program uses some of the facilities of the new class declaration for Date:

0478: // This program makes tNoelDate, a variable of              // NoelProg.java
0479: // the class type Date, refer to an object of class Date 
0480: // representing Christmas Day 1999.
0481: // It then makes tOtherDate refer to another Date object,
0482: // and then uses both == and equals to compare the two variables.
0483: import java.io. BufferedReader;
0484: import java.io. InputStreamReader;
0485: import java.io. IOException;
0486: public class NoelProg
0487: {
0488:    public static void main(final String[] pArgs) throws IOException
0489:    {
0490:       final Date tNoelDate = new Date(1999, 12, 25);
0491:       System.out.println("tNoelDate is: " + tNoelDate);
0492:       final BufferedReader tKeyboard =
0493:                     new BufferedReader(new InputStreamReader(System.in));
0494:       System.out.print("Type in the date, e.g., 1999-12-25: ");
0495:       System.out.flush();
0496:       final String tOtherDateString = tKeyboard.readLine();
0497:       final Date tOtherDate = new Date(tOtherDateString);
0498:       System.out.println("tOtherDate is: " + tOtherDate);
0499:       System.out.println("tUsingOperator: " + (tNoelDate==tOtherDate));
0500:       System.out.println("tNoelDate.equals: " + tNoelDate.equals(tOtherDate));
0501:       System.out.println("tOtherDate.equals: " + tOtherDate.equals(tNoelDate));
0502:       System.out.println("tNoelDate.iIsLeap: " + iIsLeap(tNoelDate));
0503:       System.out.println("tOtherDate.iIsLeap: " + iIsLeap(tOtherDate));
0504:    }
0505:    private static boolean iIsLeap(final Date pDate)
0506:    {
0507:       int tYear = pDate.getYear();
0508:       return (tYear%400==0) || (tYear%4==0 && tYear%100!=0);
0509:    }
0510: }