However, Java is an object-oriented language, and the idea of a class in an object-oriented language is rather different from the idea of a module in a modular language. While Java gives us the ability to use a class as a module, its main use is as a descriptor of objects. To start off on this idea, we shall consider a completely different use of a class from the way we have seen them used up till now.
We saw a number of programs which stored integers in an array, but suppose
the data we want to store in an array is not integers, but something more
complex. For example, suppose we want to read in a list of dates and store
them in an array. Java does in fact have a Date
type
provided by its library classes, but for the purpose of this exercise we
will ignore that and consider building one from scratch. A date could be
considered as being made up of three integers: a day, a month and a year.
This can be represented directly in Java by a class:
class Date { int day,month,year; }Using this representation, the following program behaves very similarly to our
Numbers0
program, simply reading a sequence of dates, storing them
in an array, and printing them out again:
1 import java.io.*; 2 3 class Dates0 4 { 5 // Read a number of dates and store them in an array. 6 7 public static void main (String[] args) throws IOException 8 { 9 Date [] data; 10 Date aDate; 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 aDate=new Date(); 27 aDate.day=d; 28 aDate.month=m; 29 aDate.year=y; 30 data[count]=aDate; 31 } 32 System.out.println("The dates entered were:"); 33 for(count=0;count<number;count++) 34 { 35 aDate=data[count]; 36 System.out.println(aDate.day+"/"+aDate.month+"/"+aDate.year); 37 } 38 } 39 }The class
Date
defines a new type Date
which may
be used elsewhere just like any other type in Java. We have already seen
object types since arrays are objects, and the type Date
behaves similarly. We may declare a variable of type Date
as
is done on line 10 of Dates0
but declaring it does not
allocate any store for it. To allocate store for the date declared on line 10,
it is necessary to use new
as is done on line 26. Since a
Date
as defined here is an object, assignment of one
Date
variable to another works by aliasing. We may have an
array of objects of type Date
, just as we may have an array
of integers, and line 9 shows the declaration of a variable to hold an array
of objects of type Date
, which is called data
.
Just like an array, which may be referred to as a whole or by its parts,
so a Date
may be referred to as a whole or by its parts. If
we have a variable of type Date
called d
, then
the year part is referred to as d.year
, the month part as
d.month
, and the day part as d.day
, with each
of these treatable as separate int
variables. In lines 26-29 above
each of these separate parts of the Date
variable
aDate
is assigned a value. Note that when the new object for
aDate
is created on line 26, the data in the old one would be
lost if it were not for data[count]
having been made to refer
to it on line 30 in the previous time round the loop of lines 17-31. Each
Date
object has its own day
, month
and
year
part, including each Date
object in the array
data
.
If you have already done some programming you may recognise this as
similar to a record
in a language like Pascal or a
struct
in C. In general, however, it is not
considered good programming practice to use classes like records, and
directly access the variables in an object. We will see better ways of
manipulating objects later on, so don't take Dates0
as an
example of good programming style!
Dates0
print out a date from the array. Strictly
it was unnecessary to use a separate variable aDate
here, we
could have just had the one line:
System.out.println(data[count].day+"/"+data[count].month+"/"+data[count].year);so we can, for example refer to the
day
component of the
component of data
indexed by count
by
data[count].dayIt would, however, be nice to have a separate method which printed out a
Date
value. We could use the following
inside class Dates0
:
public static void printDate(Date d) { System.out.println(d.day+"/"+d.month+"/"+d.year); }then we could replace lines 34-37 by:
printDate(data[count]);Note that if we just try printing each date without saying how to print something of type
Date
by replacing lines 34-37 with:
System.out.println(data[count]);the program will compile but when it is run prints what looks like just gobbledygook - the characters
Date@
followed by an assortment
of numbers and letters that bear no relationship to what was stored in
data[count]
.
A better way to go about printing objects is to have a print
method within the class of the object. That way the same print method can
be used whenever objects of that class are used, in fact the print method
can be seen as an essential part of the object, the way the object prints
itself. So, here is a revised version of Data
with a print
method called print
within it:
class Date { int day,month,year; public void print() { System.out.println(day+"/"+month+"/"+year); } }With this, lines 34-37 of
Dates0
can be replaced by:
data[count].print();Note that in this new version of class
Date
, the method
print
is not declared as static
. This is
because it is meant to be attached to individual objects of type Date
.
If we have an object d
of type Date
then the
command d.print()
will cause d
to be printed.
In object-oriented programming, this is sometimes described as "sending a
print()
message to object d
". In general, a
method is only defined as static
if there is nothing in it
which refers to the components of an individual objects of its class. Static
methods are called by attaching them with a dot to their class name, while
non-static methods are called by attaching them with a dot to the name of the
individual object on which they act. The names day
,
month
and year
within the method print
are not indicated as attached to any object, which means when
print
is called they are assumed to be attached to whichever
object the call to print
was attached to.
The print
method has a return type void
because
it does something (printing) rather than return a value. An alternative would
be to have a method which returns the String
value that should
be printed when the object is printed. This is similar to the way
writeDouble
in Text
works. In general it is more
flexible than a straight printing method. Here is a version of class
Data
with such a method:
class Date { int day,month,year; public String toString() { return day+"/"+month+"/"+year; } }With this, lines 34-37 of
Dates0
could be replaced by:
System.out.println(data[count].toString());However, it could also be replaced by just:
System.out.println(data[count]);The reason this works correctly rather than printing the goobledygook we saw before when we tried to print
Date
objects is that any object
which has in its class a non-static method called toString()
, with
no parameters, gets the toString()
method used automatically when
an attempt is made to print the object.
Date
such a constructor method would do the work which was done
on lines 26-29 of Dates0
. A constructor method has the same
name as the class of which it forms part. It may have arguments which give
the data that is used to build up an object of that class, but does not have
a return type. Here is a version of class Date
including
a constructor:
1 class Date 2 { 3 private int day,month,year; 4 5 public Date(int d,int m,int y) 6 { 7 day=d; 8 month=m; 9 year=y; 10 } 11 12 public String toString() 13 { 14 return day+"/"+month+"/"+year; 15 } 16 }Lines 5-10 here are the constructor for
Date
. Note that it
is now unnecessary to refer to the day
, month
and
year
components of an object of type Date
so these
components can be made private
as they are on line 3, which
prevents other classes from accessing them in any way (either using their
values or changing their values).
If a class has no constructor method, as with the previous version of
Date
what actually happens is that a default constructor
is assumed to exist which takes no arguments (hence the Date()
in line 26 of Dates0
) and sets all the numerical variables to 0.
It is possible to write a constructor which has no arguments, so we could
add:
public Date() { day=1; month=1; year=1900; }to the class
Date
above which means you can create a new
Date
without specifying any arguments, and in this case the
date will be the default value of 1st January 1900. Note that it is
possible to have more than one constructor in a class. Each constructor
must have the same name as the class, but the particular constructor used
when an object is created depends on the arguments specified in the call.
Below is a version of the Dates
program which uses the
constructor given above, and the toString
method to print out
the dates:
1 import java.io.*; 2 3 class Dates1 4 { 5 // Read a number of dates and store them in an array. 6 7 public static void main (String[] args) throws IOException 8 { 9 Date [] data; 10 int number,count; 11 int d,m,y; 12 BufferedReader in = Text.open(System.in); 13 System.out.print("Type the number of dates that will be entered: "); 14 number=Text.readInt(in); 15 data = new Date[number]; 16 for(count=0; count<number; count++) 17 { 18 System.out.println("Enter date "+(count+1)+": "); 19 System.out.print(" Day: "); 20 d=Text.readInt(in); 21 System.out.print(" Month: "); 22 m=Text.readInt(in); 23 System.out.print(" Year: "); 24 y=Text.readInt(in); 25 data[count] = new Date(d,m,y); 26 } 27 System.out.println("The dates entered were:"); 28 for(count=0;count<number;count++) 29 System.out.println(data[count]); 30 } 31 }
Date1
above which calls on some sorting method, similar to that
we have already seen to sort an array of integers. This line could be:
DateSorter.sort(data,count);then the code for
DateSorter
could be:
1 class DateSorter 2 { 3 4 public static void sort(Date [] a,int n) 5 // Sort (in place) the first n dates in array a. 6 // Uses selection sort 7 { 8 int sorted,minPos; 9 for(sorted=0;sorted<n;sorted++) 10 { 11 minPos=indexEarliest(a,sorted,n); 12 swap(a,sorted,minPos); 13 } 14 } 15 16 private static void swap(Date [] a,int pos1,int pos2) 17 // Swap the dates at index pos1 and pos2 in array a 18 { 19 Date temp; 20 temp=a[pos1]; 21 a[pos1]=a[pos2]; 22 a[pos2]=temp; 23 } 24 25 private static int indexEarliest(Date [] a,int pos1,int pos2) 26 // Return the index of the earliest date in the portion 27 // of array a starting at index pos1 and up to but not 28 // including index pos2. 29 { 30 int i,pos=pos1; 31 for(i=pos1+1; i<pos2; i++) 32 if(a[i].lessThan(a[pos])) 33 pos=i; 34 return pos; 35 } 36 37 }This is very similar to the
Sorter
class given in the
"Numbers" examples: part 2 notes. In fact
the main thing that has been changed is the type int
to the type
Date
where a type refers to the type of the contents of the
array being sorted. The name of the method indexSmallest
is
changed to indexEarliest
, which was not necessary but makes
the code a little easier for the human reader to understand. The method
swap
is made private
because it is not intended to
be useable outside this class. The other change is on line 32. When integers
were being sorted, they could be compared with the test
a[i]<a[pos]
. However, non-numerical values cannot be compared
with the <
operator. Instead, it is necessary to write
a comparison method for them. Here is the method, which needs to be added
to class Date
:
1 public boolean lessThan(Date d) 2 { 3 if(year<d.year) 4 return true; 5 else if(year==d.year) 6 if(month<d.month) 7 return true; 8 else if(month==d.month) 9 if(day<d.day) 10 return true; 11 return false; 12 }So the direct arithmetic test is replaced by a call to a method which returns a
boolean
value i.e. either true
or
false
. The method is attached to one Date
object
and takes another as its argument. So inside the method, for example,
year
refers to the year
value of the object to
which the call is attached (a[i]
for the call on line 32 of
DateSorter
), while d.year
refers to the
year
value of the object passed as an argument
(a[pos]
for the call on line 32 of DateSorter
).
The effect is as if the first Date
object were sent a message
containing the second Date
object inside, and asked to reply
true
or false
whether it was earlier than the
date in the message.
An alternative would be to have written lessThan
as a
static
method which takes both Date
objects
as arguments. In that case it can be considered a function which takes
two dates and returns true
or false
depending
on whether the first is earlier than the second. The code for this
alternative method is:
1 public static boolean lessThan(Date d1,Date d2) 2 { 3 if(d1.year<d2.year) 4 return true; 5 else if(d1.year==d2.year) 6 if(d1.month<d2.month) 7 return true; 8 else if(d1.month==d2.month) 9 if(d1.day<d2.day) 10 return true; 11 return false; 12 }and line 32 of
DateSorter
would be:
if(Date.lessThan(a[i],a[pos]))Note the way
lessThan
works in both cases. It first tests (on line
3) if the year of the first date is less than the year of the second, in which
case it is obviously earlier so no further tests need be made and
true
can be returned. Otherwise it only looks further comparing
the month, if the years are equal, and then only looks at the days if the
months are equal as well. Line 10 which returns false
is only
reached if none of the previous lines which return true
(4, 7 and
10) were reached, since when they are reached the return
causes
execution of lessThan
to finish and line 11 isn't reached.
Since the code for DateSorter
has exactly the same structure
as that for Sorter
it uses the same selection sort algorithm.
In fact it seems a bit of a waste of effort to have to go through the
original Sorter
class making minor changes in order to sort
dates rather than integers. Later on we shall show how it is possible to
write a generic sorter, that is one that is not restricted to
arrays of a particular base type but instead can be used to sort an array
of any type.
1 import java.io.*; 2 3 class Dates2 4 { 5 // Read a number of 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 Date [] 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 Date[MAXDATES]; 29 try 30 { 31 for(;;) 32 data[count++] = readDate(inFile); 33 } 34 catch(IOException e) 35 { 36 } 37 catch(ArrayIndexOutOfBoundsException e) 38 { 39 System.out.print("Maximum number of dates ("+MAXDATES); 40 System.out.println(") exceeded"); 41 } 42 count--; 43 DateSorter.sort(data,count); 44 System.out.println("The dates read are (after sorting):"); 45 for(int i=0;i<count;i++) 46 System.out.println(data[i]); 47 } 48 49 public static Date readDate(BufferedReader reader) 50 throws IOException 51 { 52 int d,m,y; 53 d=Text.readInt(reader); 54 m=Text.readInt(reader); 55 y=Text.readInt(reader); 56 return new Date(d,m,y); 57 } 58 59 }As before, a
try
statement inside an infinite loop
(lines 16-27) is used in order to have the program keep on prompting for the
name of a file until it is given one it can open. Then an infinite loop inside
a try
statement (lines 29-41) is used to carry on reading
dates until the end of the file is reached or the number of dates exceeds
the maximum number storable in the previously created (on line 28) array.
As the array is created before the number of dates read is known, it
has to be of the maximum size considered necessary, and there is a
catch
clause (lines 37-40) which comes into action if an
attempt is made to exceed this limit. The limit is declared as a constant
(or static final
) on line 8.
A separate method to read dates from a file, called readDate
,
is given, on lines 49-57. As the numbers are read from a file there are
no prompts, but note it expects the numbers to be expressed just as
three integers: the day, the month and the year. You cannot put a slash or
any other character except a space between then, as the program is not
equipped to deal with that. The BufferedReader
which was
linked with the input file on line 21 is passed as an argument to
readDate
, and then passed as an argument to readInt
from Text
to get the integer to be read from the file.
The method readDate
will
throw
an IOException
if it encounters one. That
is indicated by the throw
clause on line 50. What this means
is that if an IOException
occurs in the call to
readDate
, which is on line 32, it is not dealt with inside
this method, but thrown up so it is dealt with in the try
statement of which line 32 is part. This try
statement has
a clause to deal with IOException
, on lines 34-36. In fact it
simply reacts by doing nothing. An end-of-file exception is a kind of
IOException>
, so what this means is that if readDate
attempts to read past the end of a file, execution of readDate
finishes, wherever it has got to, and execution immediately goes to line 35.
As there is nothing there, it goes on to the next statement after the
try
statement, which is on line 42. Note the use of ++
on line 32 to increase the value in the variable count
by one
each time round the loop. Line 42 decreases the value in count
by one, as the increase before the IOException
was called would
have been one too many. The effect of this is that the loop on lines 31-32
is correctly exited when the end of the input file is encountered. Note that
this works because EOFException
is a form of
IOExcpeption
The program as given does not contain any tests for whether the integers read form valid dates. This would obviously be a useful modification, ensuring the data read in are valid. We will consider it later. The programs discussed in these notes can be found in the directory:
/import/teaching/BSc/1st/ItP/datesincluding a file called
dates
which could be used in
conjunction with the program in Dates2.java
.
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: 14 Oct 1998