The Dates Examples: Part 3

Immutable objects

Let us remind ourselves of the type Date we discussed in the "Dates" example. Here is the Java code for the class Date:
class Date
{
 private int day,month,year;

 public Date(int d,int m,int y) throws DateException
 {
  day=d;
  month=m;
  year=y;
  if(month<1||month>12||day<1)
     throw new DateException(toString());
  else if((month==4||month==6||month==9||month==11)&&day>30)
     throw new DateException(toString());
  else if(month==2)
     {
      if(year%4==0)
         {
          if(day>29)
             throw new DateException(toString());
         }
      else if(day>28)
         throw new DateException(toString());
     }
  else if(day>31)
      throw new DateException(toString());
 }

 public String toString()
 {
  return day+"/"+month+"/"+year;
 }

 public boolean lessThan(Date d)
 {
  if(year<d.year)
     return true;
  else if(year==d.year)
     if(month<d.month)
        return true;
     else if(month==d.month)
        if(day<d.day)
           return true;
  return false;
 }

}
This means that every Date object has three fields, day, month and year. A new date object is created by using the expression new Date(x,y,z) where x, y and z are each integer expressions. The constructor is written so that if these do not form a valid date, a DateException is thrown. The method toString() enables objects of type Date to be printed in the day/month/year format, with month being an integer from 1 to 12. The method lessThan(Date d) enables us to compare dates to see which is the earliest.

With this code, objects of type Date are immutable. That means that once you have created one with new, it is not possible to change the values stored in its fields. This is often a valuable property: if you know it is impossible to change the data stored in some object, you know that it is impossible to have the sort of programming error caused by some method somewhere unexpectedly changing an object's data.

Mutable objects

However, suppose we really do want dates that can be changed in a program. One way would be simply to make the fields in the class Date public. This can be done by replacing the word private with public where the fields are declared, making it:
 public int day,month,year;
In fact, having nothing but
 int day,month,year;
will have the same effect, at least in Java as we have seen it in this course (the difference only comes into effect if we move on to the Java concept of packages, which we have not considered yet). Then we can freely use the fields of some Date object as if they were variables, reading and writing to them. For example, suppose we want to change the value stored in some date object called myDate so that it represents the day after the day it originally represented. If the fields are public this could, you might think, be done by:
   myDate.day++;
but suppose the date happened to represent the last day of the month. If the date stored in variable myDate was the 31st October 1998, we would end up with myDate storing the non-existent 32nd October 1998. If we are changing a date by one day, but the date is the last day of the month, we need to set the day field to 1 and change the month field to the next month (or set the month and day field to 1 and add one to the year field if the date we are updating happens to be New Year's Eve). Of course we can adjust our code to do all this:
   myDate.day++;
   if(myDate.day==32)
      {
       if(myDate.month==12)
          {
           myDate.month=1;
           myDate.year++;
          }
       else
          myDate.month++;
       myDate.day=1;
     }
  else if(myDate.day==31&&
          (myDate.month==4||myDate.month==6||myDate.month==9||myDate.month=11))
     {
      myDate.month++;
      myDate.day=1;
     }
  else if(myDate.month==2)
     if(myDate.year%4==0&&myDate.day==30)
        {
         myDate.month=3;
         myDate.day=1;
        }
     else if(myDate.day==29)
        {
         myDate.month=3;
         myDate.day=1;
        }
This almost works, only it misses out dealing with years ending in 00 not always being leap years. However, that is besides the point. If it is possible to change a field in a Date object to anything, it is possible to change the data stored to something that is no longer a valid date. Although this would be a programming error, it would be better to make sure such errors couldn't happen than rely on programmers always making sure to avoid them.

Mutator methods

The above example indicates why it is generally not a good idea to have the fields in an object made public so they can be altered by other objects. The idea of object-oriented programming is that objects should be self-contained, so that objects can effect each other only by sending messages to each other (remember that "a sends message m to b" is one way of talking about what happens when the method call b.m is executed inside one of a's methods). In this way, objects have complete control of their own data and can ensure it does not get changed in any unexpected way. So with dates, for example, the data within a Date object should only be changed by a method attached to the Date object. We would not allow the day, month and year field to be accessible by other objects, but they could change them indirectly, say by calling an addDay() method.

A method attached to an object in this way, which allows other objects to change their values but in a controlled way, is called a mutator method. Below is a class called Calendar which extends the Date class by adding mutator methods to it. The mutator methods allow the date in a Calendar object to be put forward or back by a day, a month or a year.

class Calendar extends Date
{

 public Calendar(int d,int m,int y) throws DateException
 {
  super(d,m,y);
 }

 public void addDay()
 {
  day++;
  if(day==32)
    {
     day=1;
     month++;
    }
  else if(day==31&&(month==4||month==6||month==9||month==11))
    {
     day=1;
     month++;
    }
  else if(day==30&&month==2)
    {
     day=1;
     month=3;
    }
  else if(day==29&&month==2&&year%4!=0)
    {
     day=1;
     month=3;
    }
  if(month==13)
    {
     month=1;
     year++;
    }
 }
 
 public void subDay()
 {
  day--;
  if(day==0)
    {
     month--;
     if(month==0)
       {
        month=12;
        day=31;
        year--;
       }
     else if(month==4||month==6||month==9||month==11)
       day=30;
     else if(month==2)
        if(year%4==0)
           day=29;
        else
           day=28;
     else
        day=31;
    }
 }

 public void addMonth()
 {
  month++;
  if(month==13)
     {
      month=1;
      year++;
     }
  else if(day==31&&(month==4||month==6||month==9||month==11))
     day=30;
  else if(month==2)
     if(year%4==0)
       {
        if(day>29)
           day=29;
       }
     else if(day>28)
        day=28;
 }

 public void subMonth()
 {
  month--;
  if(month==0)
     {
      month=12;
      year--;
     }
  else if(day==31&&(month==4||month==6||month==9||month==11))
     day=30;
  else if(month==2)
     if(year%4==0)
       {
        if(day>29)
           day=29;
       }
     else if(day>28)
        day=28;
 }

 public void addYear()
 {
  year++;
  if(month==2&&day==29)
     day=28;
 }

 public void subYear()
 {
  year--;
  if(month==2&&day==29)
     day=28;
 }

}
The code to change the month by one is complicated by the fact that months have different lengths, so for example if we're adding a month to the 31st August, we have to be careful to get the 30th September rather than the non-existent 31st September. Even changing a year requires attention to the fact that we need to change the day if we happen to be adding or taking a year from 29th February on a leap year.

A file containing this code may be found in the shared directory:

/import//teaching/BSc/1st/ItP/calendars
A new version of the code for class Date is also there. The only change is that the word private on the third line is changed to protected. The reason for this is that fields declared as private are too restricted for the use we require here. Access to them is restricted only to the actual methods in the class itself, so methods in classes that inherit from them cannot access them. A protected field declaration, however, means the field can be freely accessed and changed in classes which inherit it, although nowhere else. This is what is required in Calendar: it has to be able to access and change the values of the day, month and year fields it inherits, but code that uses objects of type Calendar must not.

A simple program that uses the Calendar class is put in the directory with it, in the file CalendarChange.java. Note this program uses the Text class, so you must have the file Text.class in your directory to run it (you may find a copy in /import/teaching/BSc/1st/ItP). Here is an example of the program in CalendarChange.java working:

Enter day: 28
Enter month: 2
Enter year: 1975
Enter + or - to add or subtract followed by d, w, m, or y (or q to quit)
> +d 
1/3/1975
> +y
1/3/1976
> -d
29/2/1976
> -m
29/1/1976
> -m
29/12/1975
> q
29/12/1975
A version of this is given which adds a graphical user interface element in its display of dates. The class Calendar1 extends Calendar further by adding a graphical display field. The code to deal with the graphical display field is found in file CalendarGraphic.java. As we are not covering graphics in this course you need not be concerned with how that code works, you just need a copy of it (or rather the version compiled into Java byte code) in your directory. A version of the CalendarChange.java program which uses Calendar1 objects rather than Calendar objects can be found in file CalendarChange1.java.

Data Hiding

Once we have agreed that the actual fields in an object should not be available for public manipulation, an interesting aspect emerges. It does not matter how the data is represented internally in an object, from the outside an object can only be viewed in terms of the methods it has. This means we can completely change the internal representation of an object and alter the object's methods to deal with the new internal representation, while leaving any programs that make use of the object unchanged. This is known as data hiding. It is an important idea because obviously it is very convenient if when we decide to change the way some data is stored we can do so simply in just the relevant object without having to go through large amounts of code elsewhere making changes.

As a good example, although we think of dates as having a day, month and year part, it can make a lot of sense to store them in a different way - as the number of days past some fixed date. For example, if we take 1st January 1900 as day 1, then 29th October 1998 is day 36096 - thirty-six thousand and ninety-five days later. If we use this representation of dates, then some operations are very simple to program. For example, finding whether one date is less than another involves simply comparing two numbers. Adding a day to a date involves simply adding one to a number. However, if we want to print a date out which is stored in this form, it involves some complex calculations in order to bring it back to the day/month/year format. Here is a version of Date which stores dates in this way:

class Date
{
 protected int days;

 public Date(int day,int month,int year) throws DateException
 {
  if(month<1||month>12||day<1)
     throw new DateException(toString());
  else if((month==4||month==6||month==9||month==11)&&day>30)
     throw new DateException(toString());
  else if(month==2)
     {
      if(year%4==0)
         {
          if(day>29)
             throw new DateException(toString());
         }
      else if(day>28)
         throw new DateException(toString());
     }
  else if(day>31)
      throw new DateException(toString());
  days=(year-1900)*365+(year-1901)/4;
  days+=day;
  if(month>=2)
     {
     days+=31;
     if(month>=3)
        {
         if(year%4==0&&year!=1900)
            days+=29;
         else
            days+=28;
         switch(month)
            {
             case 3: break;
             case 4: days+=31; break;
             case 5: days+=61; break;
             case 6: days+=92; break;
             case 7: days+=122; break;
             case 8: days+=153; break;
             case 9: days+=184; break;
             case 10: days+=214; break;
             case 11: days+=245; break;
             case 12: days+=275;
            }
        }
     }
 }

 public String toString()
 {
  int year=days/365;
  int day=days%365;
  int month;
  if(year!=0)
     day-=(year-1)/4;
  if(day<=0)
     {
      if((year-1)%4==0)
         day+=366;
      else
         day+=365;
      year--;
     }
  if(day<=31)
     month=1;
  else if(day<=59)
     {
      month=2;
      day-=31;
     }
  else if(year%4==0&&day==60&&year!=0)
     {
      month=2;
      day=29;
     }
  else
     {
      if(year%4==0&&year!=0)
         day-=60;
      else
         day-=59;
      if(day<=31)
         month=3;
      else if(day<=61)
         {
          month=4;
          day-=31;
         }
      else if(day<=92)
         {
          month=5;
          day-=61;
         }
      else if(day<=122)
         {
          month=6;
          day-=92;
         }
      else if(day<=153)
         {
          month=7;
          day-=122;
         }
      else if(day<=184)
         {
          month=8;
          day-=153;
         }
      else if(day<=214)
         {
          month=9;
          day-=184;
         }
      else if(day<=245)
         {
          month=10;
          day-=214;
         }
      else if(day<=275)
         {
          month=11;
          day-=245;
         }
      else
         {
          month=12;
          day-=275;
         }
     }
  year+=1900;
  return day+"/"+month+"/"+year;
 }

 public boolean lessThan(Date d)
 {
  return (days<d.days);
 }

}
As you can see, converting dates to and from the day/month/year format is a little complex, due to the varying number of dates in a year, and the extra complexity brought in by leap year. But the lessThan operator is very simple. The protected field days stores the actual representation of a date.

You could experiment by compiling this version of Date and showing that most of the previous programs which use the class Date still run and give exactly the same result. The only difference (which you won't see as the programs are not big enough to make it noticeable) is that dates stored in this way take less space, and the timing of the operations is different (lessThan can be done more quickly, but reading and writing dates is slower).

Programs using the class Calendar won't work if we change the internal representation in class Date though. This is because class Calendar does rely on the internal representation of dates having a day, a month and a year field.

Code for dates stored in this new way is given in the directory /import/teaching/BSc/1st/ItP/calendars, though it is given as a separate type, Date1. A version of the Calendar type is also given, called Calendar2, adding the method addDate(int n). Here it is:

class Calendar2 extends Date1
{

 public Calendar2(int d,int m,int y) throws DateException
 {
  super(d,m,y);
 }

 public void addDay(int n)
 {
  days+=n;
 }

}
The method addDate(int n) enables you to add a number of days to a date (the number may be negative so it also enables you to take days from a date). As you can see, it is very easy to implement given the representation of dates as just a number of days. Methods to add months or years would be complex given the day representation, but this method would be useful if we had a program where a common operation was to give a date a certain number of days after another. The program in CalendarChange2.java makes use of it. Here is an example of it in operation:
Enter day: 29
Enter month: 10
Enter year: 1998
29/10/1998
Enter + to add a day, - to subtract a day
followed by a number for multiple days
Or enter q to quit
> +1
30/10/1998
> +1
31/10/1998
> +1
1/11/1998
> -1000
5/2/1996
So it easily calculates that the day one thousand days before 1st November 1998 was 5th February 1996. Doing the calculation on the representation merely involved subtracting 1000 from 36099, the complicated bit was finding the day/month/year represented by day 35099. The program in CalendarChange3.java works similarly, making use of the Date1 representation of dates, but has the graphics display we used previously in CalendarChange1.java. Graphics are added to objects of type Calendar2 by Calendar3.

Inheritance Diagrams

At this point it can be seen that the details of which class inherits from which can become complex. In some languages it is possible for a class to inherit from more than one class (multiple inheritance), but in Java a class can only have one superclass. This enables us to represent inheritance by a tree diagram in which each node represents a class, with one link to its superclass, and possibly several links coming in to it from subclasses. So far we have had class Date which is the superclass of Calendar considered here, as well as BirthDate considered in the "Dates": part 2 example. Calendar1 in turn has Calendar as its superclass, giving us the diagram:
			Date
		      /      \
	        BirthDate  Calendar
	                      |
	                   Calendar1
We have also had Calendar2 inheriting from Date1 and Calendar3 inheriting from Calendar2, giving us:
			   Date1
			     |
			  Calendar2
			     |
			  Calendar3
In fact in Java all objects which don't inherit from any other class have a predefined class called Object as their superclass, so the inheritance diagram can be written as:
			Object
                       /      \
	           Date1     Date
                    /        /   \
             Calendar2 BirthDate  Calendar
                 /                  \
          Calendar3                Calendar1
We shall see later where Object can be useful.
Matthew Huntbach

These notes were produced as part of the course Introduction to Programming as it was given in the Department of Computer Science at Queen Mary, University of London during the academic years 1998-2001.

Last modified: 29 Oct 1998