Dates2
example, that reads a file of names and birthdates,
stores them in an array, and sorts them according to age.
1 import java.io.*; 2 3 class BirthDates1 4 { 5 // Read a number of birth dates from a file, store them in an array 6 // and sort them 7 8 static final int MAXDATES=100; 9 10 public static void main(String[] args) throws IOException 11 { 12 String filename; 13 BirthDate [] data; 14 int count=0; 15 BufferedReader in = Text.open(System.in),inFile; 16 for(;;) 17 try 18 { 19 System.out.print("\nEnter file name to read from: "); 20 filename=Text.readString(in); 21 inFile=Text.open(filename); 22 break; 23 } 24 catch(FileNotFoundException e) 25 { 26 System.out.println("No file of that name found"); 27 } 28 data = new BirthDate[MAXDATES]; 29 try 30 { 31 for(;;) 32 data[count++] = readBirthDate(inFile); 33 } 34 catch(IOException e) 35 { 36 } 37 catch(ArrayIndexOutOfBoundsException e) 38 { 39 System.out.print("Maximum number of birthdates ("+MAXDATES); 40 System.out.println(") exceeded"); 41 } 42 count--; 43 DateSorter.sort(data,count); 44 System.out.println("The birthdates read are (after sorting):"); 45 for(int i=0;i<count;i++) 46 System.out.println(data[i]); 47 } 48 49 public static BirthDate readBirthDate(BufferedReader reader) 50 throws IOException 51 { 52 int d,m,y; 53 String n; 54 d=Text.readInt(reader); 55 m=Text.readInt(reader); 56 y=Text.readInt(reader); 57 n=Text.readString(reader); 58 return new BirthDate(d,m,y,n); 59 } 60 61 }A suitable file for use with this program is given in the directory containing the
dates
examples, called birthdates
.
It might be thought this would require the writing of a new class for
BirthDate
something like:
class Birthdate { int day,month, year; String name; ... code for all the methods required }which would duplicate a lot of the effort used in writing the class
Date
. However, Java, in common with other object-oriented
languages, has a way in which a class may be written which is based on
a class which is already written. This is called inheritance.
Inheritance extends the idea of reuse we have already mentioned.
Inheritance allows us not only to reuse classes we have already defined
anywhere where the same class would be useful, but also to reuse classes
by extending them with added detail to construct new classes. Here is the Java
code for BirthDate
which is actually used by the program
BirthDates1
above:
1 class BirthDate extends Date 2 { 3 String name; 4 5 public BirthDate(int d,int m,int y,String n) 6 { 7 super(d,m,y); 8 name=n; 9 } 10 11 public String toString() 12 { 13 return super.toString()+" "+name; 14 } 15 16 }Line 1 says that
BirthDate
specialises Date
.
Another way of putting it is to say that Date
is a
superclass of BirthDate
or, alternatively,
BirthDate
is a subclass of BirthDate
.
A third way of putting it is to say that a BirthDate
"is a kind of"
Date
.
The effect is that a BirthDate
object has all the attributes of
a Date
object, that is a year
, a month
and a day
field, all of type int
, but in addition
a name
field of type String
as indicated on line
3 of BirthDate
. It also has the same methods as a Date
object, except where they have been overridden. A method is
overridden in a subclass if the subclass has a new method of the same name
and arguments. So here lines 11-14 override the original toString()
of Date
. However, lessThan(Date d)
is not
overridden. This means that BirthDate
objects can be compared
using lessThan
which will behave just like they were
Date
objects - you do not have to mention it in the code for
class BirthDate
since it is implied by the extends Date
on line 1. Since the code for BirthDate
makes use of the
code for Date
, that code has to be available for its use.
For now, that can be done by having the file Date.class
in the same directory as BirthDate.java
when
BirthDate.java
is compiled (you do not need to have
Date.java
available).
The overriding of toString
means that a BirthDate
object has its own toString
method, one which prints the
name as well as the date. However, in this case the new method makes use
of the old method, indicated by super.toString()
on line 13.
A call to a method prefixed by super.
means use the method
from the superclass of the class it is being called in.
The constructor for BirthDate
objects, given on lines 5-9
also makes use of the equivalent for Date
objects. The call
to super
on line 7 calls the constructor method for
Date
first, which assigns its argument d
to the
day
field, m
to the month
field and
y
to the year
field, but after doing this does, on
line 8, the additional assignment of n
to the name
field. A constructor for a subclass must either call super
on its first line in this way, or have the effect of calling
super()
(i.e. the constructor with no arguments) if it is not
given (there will be a compiler error if no call to super
is
given, but the superclass does not have a zero-argument constructor).
One particularly useful thing about inheritance is that code written to deal
with objects of the superclass can also deal with objects of the subclass.
A good example is shown here in the BirthDates1
example. On
line 43 you can see that the call to the sort
method of the
DateSorter
class is just the same as that used previously to
sort an array of dates - we don't have to write any new sorting methods as
the code to sort an array of objects of type Date
can be used
to sort an array of objects of type BirthDate
.
sort
example used the lessThan
method from
class Date
for objects of type BirthDate
because
BirthDate
inherited it from Date
. But what if
BirthDate
had its own lessThan
method? We could
give it one which is different from the lessThan
method of
Date
by adding the following method to the code for class
BirthDate
:
1 public boolean lessThan(Date date) 2 { 3 if(date instanceof BirthDate) 4 return (name.compareTo(((BirthDate) date).name)<0); 5 else 6 return super.lessThan(date); 7 }This version of
lessThan
is written to make BirthDate
objects compare each other using alphabetic ordering of the name field attached
to each date, rather than the date order of the year, month and day fields.
If you add this method to the file BirthDate.java
, recompile
that file, and run BirthDates1
again, you will find the
names and dates are printed out with the names in alphabetical order. By
changing just BirthDate
, the sorting method changes from sorting
by date to sorting by name even though you didn't change or even recompile the
code for it.
The reason for this is that a program which deals with objects of type
Date
will use the lessThan
method of
BirthDate
if there is one and the objects it is comparing are
actually BirthDate
objects when the code is run - so it doesn't
decide which actual method to use until the code is run (hence "late" or
"dynamic" binding of method names to actual methods specified in code).
Note that in order for this new version of lessThan
to override
and hence replace the version in class Date
it has to have
the same header (in line 1) as that in Date
, in this case
public
boolean
lessThan(Date
date)
. It wouldn't override if it had the header
public
boolean
lessThan(BirthDate
date)
. Because of this it has to test whether its argument
date
really is of type BirthDate
- this is done
on line 3 using the operator instanceof
which is actually a
key word in Java. The test x
instanceof
t
where x
is a Java expression, and t
a class name is true if the expression evaluates to an instance of the
class name. If the Date
object date
being
compared with the BirthDate
object is not itself of type
BirthDate
, the lessThan
test of Date
is used, indicated on line 6, so comparison is still by date.
Line 4 gives the comparison of names. It is complicated because first of
all it is necessary to convert date
to its real
BirthDate
type, which is done using type casting (which we saw
previously to do things like turn an int
into a
double
), hence (BirthDate)
date
.
Having done that, the name
field is taken from the
BirthDate
object, and passed as an argument to the
compareTo
method of the name
field of the
object lessThan
is attached to. Remember that the name
field is a String
and String
s are themselves
objects. One of the methods in class String
is
compareTo
which takes another String
as an
argument and returns an integer as the result. The integer is less than
zero if the first String
comes before the second alphabetically,
it is 0 is they are identical, and greater than 0 otherwise. The arithmetic
operators <
and >
don't work with
String
s. If this is confusing, here is an extended version
of the new lessThan
:
1 public boolean lessThan(Date date) 2 { 3 BirthDate birthdate; 4 String birthdatename; 5 int comp; 6 boolean testresult; 7 if(date instanceof BirthDate) 8 { 9 birthdate = (BirthDate) date; 10 birthdatename=birthdate.name; 11 comp=name.compareTo(birthdatename); 12 testresult=(comp<0); 13 return testresult; 14 } 15 else 16 return super.lessThan(date); 17 }It does exactly the same, but breaks what is done just on line 4 above into its component steps on lines 9-13, requiring the additional variables declared on lines 3-6 to hold the results of each step.
Here is a version of the constructor method to be used in the class
Date
which throws an exception we have decided to call
DateException
:
1 public Date(int d,int m,int y) throws DateException 2 { 3 day=d; 4 month=m; 5 year=y; 6 if(month<1||month>12||day<1) 7 throw new DateException(toString()); 8 else if((month==4||month==6||month==9||month==11)&&day>30) 9 throw new DateException(toString()); 10 else if(month==2) 11 { 12 if(year%4==0) 13 { 14 if(day>29) 15 throw new DateException(toString()); 16 } 17 else if(day>28) 18 throw new DateException(toString()); 19 } 20 else if(day>31) 21 throw new DateException(toString()); 22 }This is almost correct except it does not deal with the variation on leap years which means that years of the form xx00 are not leap years unless the number xx is divisible by 4. Correcting it to deal with that is left as an exercise.
To indicate that a call new Date(d,m,y)
may cause a
DateException
the words throws
DateException
are added to the end of line 1. A new
DateException
is created on lines 9, 15, 18 and 21. When
a new exception is created it takes as an argument some string detailing
the problem. In this case, the string is just the print-out of the date as
given by its toString()
method. The keyword throw
is used to indicate a user-programmed exception throw. Like a system
exception the effect is that execution immediately halts and goes to the
place where the exception is caught.
Having used DateException
we need to have a separate class
defining it. Here it is:
1 class DateException extends Exception 2 { 3 DateException(String s) 4 { 5 super(s); 6 } 7 }The object type
Exception
is built in to Java. A new
exception can be defined by extending this type, as indicated on
line 1 of DateException
. Apart from its name a
DateException
is just like an Exception
so
it simply calls its super
constructor with its own
string argument and has no other methods.
To illustrate the use of DateException
we give a version
of the program which simply asked for a number of dates and read them in as
typed by the user:
1 import java.io.*; 2 3 class Dates3 4 { 5 6 // Read a number of dates and store them in an array. 7 8 public static void main (String[] args) throws IOException 9 { 10 Date [] data; 11 int number,count; 12 int d,m,y; 13 BufferedReader in = Text.open(System.in); 14 System.out.print("Type the number of dates that will be entered: "); 15 number=Text.readInt(in); 16 data = new Date[number]; 17 for(count=0; count<number; count++) 18 { 19 System.out.println("Enter date "+(count+1)+": "); 20 System.out.print(" Day: "); 21 d=Text.readInt(in); 22 System.out.print(" Month: "); 23 m=Text.readInt(in); 24 System.out.print(" Year: "); 25 y=Text.readInt(in); 26 try 27 { 28 data[count] = new Date(d,m,y); 29 } 30 catch(DateException e) 31 { 32 System.out.println("Invalid date: "+e.getMessage()); 33 count--; 34 } 35 } 36 DateSorter.sort(data,count); 37 System.out.println("The dates entered were:"); 38 for(count=0;count<number;count++) 39 System.out.println(data[count]); 40 } 41 42 }Lines 26-34 show where the original program (
Dates1
) was
modified to catch DateException
s. Note that an
exception has a method, getMessage()
which is used to give
the string that was given when the exception was created. It is used on line
32 to print the invalid date. As we don't want invalid dates to be counted,
line 33 decreases the value in the variable count
by 1 so that
next time round the loop count
has the same value as previously
rather than the next one.
Having modified Date
so that it throws DateException
s,
we have to modify BirthDate
so that it can deal with them:
1 class BirthDate extends Date 2 { 3 String name; 4 5 public BirthDate(int d,int m,int y,String n) throws DateException 6 { 7 super(d,m,y); 8 name=n; 9 } 10 11 public String toString() 12 { 13 return name+" "+super.toString(); 14 } 15 16 }As you can see, the only change made is on line 5, meaning that if a
DateException
occurs when the call to the superconstructor on
line 7 is made, it is just thrown back to whatever called the constructor
for BirthDate
. Note that here a DateException
is treated just like any other checked exception - as it is not caught you
have to indicate on the method header that it may be thrown, and it is
automatically thrown if it occurs. Here is the code for a new version of
the BirthDates
program which deals with invalid dates by catching
the DateException
s:
1 import java.io.*; 2 3 class BirthDates2 4 { 5 // Read a number of birth dates from a file, store them in an array 6 // and sort them 7 8 static final int MAXDATES=100; 9 10 public static void main(String[] args) throws IOException 11 { 12 String filename; 13 BirthDate [] data; 14 int count=0; 15 BufferedReader in = Text.open(System.in),inFile; 16 for(;;) 17 try 18 { 19 System.out.print("\nEnter file name to read from: "); 20 filename=Text.readString(in); 21 inFile=Text.open(filename); 22 break; 23 } 24 catch(FileNotFoundException e) 25 { 26 System.out.println("No file of that name found"); 27 } 28 data = new BirthDate[MAXDATES]; 29 try 30 { 31 for(;;) 32 try 33 { 34 data[count++] = readBirthDate(inFile); 35 } 36 catch(DateException e) 37 { 38 System.out.println("Invalid date "+e.getMessage()); 39 count--; 40 } 41 } 42 catch(IOException e) 43 { 44 } 45 catch(ArrayIndexOutOfBoundsException e) 46 { 47 System.out.print("Maximum number of birthdates ("+MAXDATES); 48 System.out.println(") exceeded"); 49 } 50 count--; 51 DateSorter.sort(data,count); 52 System.out.println("The birthdates read are (after sorting):"); 53 for(int i=0;i<count;i++) 54 System.out.println(data[i]); 55 } 56 57 public static BirthDate readBirthDate(BufferedReader reader) 58 throws IOException,DateException 59 { 60 int d,m,y; 61 String n; 62 d=Text.readInt(reader); 63 m=Text.readInt(reader); 64 y=Text.readInt(reader); 65 n=Text.readString(reader); 66 return new BirthDate(d,m,y,n); 67 } 68 69 }A
DateException
will occur when a call to create a new
BirthDate
throws one. In this case it is thrown yet again, as
indicated on line 58. It is caught on line 36. The single statement on
line 34 may cause a DateException
if an invalid date is
entered, an IOException
if an attempt is made to read past
the end of the input file, or an ArrayIndexOutOfBoundsException
if an attempt is made to read more birthdates than the maximun storeable in
the array. But a DateException
is caught inside the
loop, so that execution carries on reading birthdates (again, the variable
count
has to be decreased by 1), whereas the other two exceptions
are caught outside the loop, on lines 42 and 45, causing execution to exit
the loop.
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: 21 Oct 1998