1 import java.util.*; 2 import java.io.*; 3 4 class Student extends Person 5 { 6 static int count=0; 7 static final int MAXMARKS=30; 8 int numSubjects,studentNum; 9 int[] marks; 10 String firstNames; 11 12 public Student(int d,int m, int y,String n1,String n2,int num,int marks[]) 13 throws PersonException 14 { 15 super(d,m,y,n1); 16 numSubjects=num; 17 this.marks=marks; 18 firstNames=n2; 19 studentNum=++count+10000; 20 } 21 22 public String toString() 23 { 24 String marksString=""; 25 for(int i=0;i<numSubjects;i++) 26 marksString+=" "+marks[i]; 27 return "Name: "+firstNames+" "+name+ 28 "\nDate of birth: "+dateOfBirth+ 29 "\nStudent number: "+studentNum+ 30 "\nMarks: "+marksString+"\n"; 31 } 32 33 public int totalMarks() 34 { 35 int myMarks=0; 36 for(int i=0;i<numSubjects;i++) 37 myMarks+=marks[i]; 38 return myMarks; 39 } 40 41 public double averageMark() 42 { 43 return (double)totalMarks()/numSubjects; 44 } 45 46 public boolean lowerTotalMarksThan(Student s) 47 { 48 return (totalMarks()<s.totalMarks()); 49 } 50 51 public boolean lowerAverageMarkThan(Student s) 52 { 53 return (averageMark()<s.averageMark()); 54 } 55 56 }A copy of the Java code for this class can be found in the directory
/import/teaching/BSc/1st/ItP/peopleAs you can see from line 1, the class
Student
extends the
class People
, so People
is a superclass of
Student
. This means that Student
objects have
a name
field and a dateOfBirth
field,
inherited from Person
. They also inherit the older
and beforeAlphabetically
methods from Person
.
This means that students can be compared using these methods, and arrays
of students may be sorted using the PeopleSorter
class
developed in the People notes.
Students have the additional information of their marks, stored as the
field marks
declared on line 9 above. The marks
field is an array of integers. Different students may
have taken different numbers of exams, so we do not assume every student
has the same number of marks, rather we have a separate field,
numSubjects
declared on line 8 to say how many marks a
particular student has. Additionally another integer field
studentNum
is declared to hold a unique number for each student,
and a string field firstNames
is declared on line 10 to hold
students' first names. The idea is that the name
field
inherited from Person
will only store the surname. The
toString
method on lines 22-31 overrides the toString
method inherited from Person
. This means that when a
Student
object is printed, it is printed according to the
toString
method in class Student
, not according
to the toString
method in class Person
.
Here is a program, similar to the People programs, which reads in a collection of student records from a file, sorts them by name, and prints them:
1 import java.io.*; 2 import java.util.*; 3 4 class Students 5 { 6 // Read a number of Student records from a file, store them in an array 7 8 static final int MAXSTUDENTS=100; 9 static final int MAXMARKS=30; 10 11 public static void main(String[] args) throws IOException 12 { 13 String filename; 14 Student [] data; 15 int count=0; 16 BufferedReader in = Text.open(System.in),inFile; 17 for(;;) 18 try 19 { 20 System.out.print("\nEnter file name to read from: "); 21 filename=Text.readString(in); 22 inFile=Text.open(filename); 23 break; 24 } 25 catch(FileNotFoundException e) 26 { 27 System.out.println("No file of that name found"); 28 } 29 data = new Student[MAXSTUDENTS]; 30 try 31 { 32 for(;;) 33 try 34 { 35 data[count++] = readStudent(inFile); 36 } 37 catch(PersonException e) 38 { 39 System.out.println(e.getMessage()); 40 count--; 41 } 42 } 43 catch(IOException e) 44 { 45 } 46 catch(ArrayIndexOutOfBoundsException e) 47 { 48 System.out.print("Maximum number of students ("+MAXSTUDENTS); 49 System.out.println(") exceeded"); 50 } 51 count--; 52 new PeopleSorter(new PeopleOrderByName()).sort(data,count); 53 System.out.println("\nThe records are:\n"); 54 for(int i=0;i<count;i++) 55 System.out.println(data[i]); 56 } 57 58 public static Student readStudent(BufferedReader reader) 59 throws IOException,PersonException 60 { 61 int day,month,year,numMarks; 62 String name,marksString,dateString,nameString,firstNames; 63 StringTokenizer tokens; 64 int[] marks; 65 nameString=reader.readLine(); 66 if(nameString==null) 67 throw new IOException(); 68 tokens = new StringTokenizer(nameString); 69 firstNames = tokens.nextToken(); 70 name=tokens.nextToken(); 71 while(tokens.hasMoreTokens()) 72 { 73 firstNames+=" "+name; 74 name=tokens.nextToken(); 75 } 76 dateString=reader.readLine(); 77 tokens = new StringTokenizer(dateString,"/"); 78 day=Integer.parseInt(tokens.nextToken()); 79 month=Integer.parseInt(tokens.nextToken()); 80 year=Integer.parseInt(tokens.nextToken()); 81 marks = new int[MAXMARKS]; 82 marksString=reader.readLine(); 83 tokens = new StringTokenizer(marksString); 84 numMarks=0; 85 while(tokens.hasMoreTokens()) 86 marks[numMarks++]=Integer.parseInt(tokens.nextToken()); 87 return new Student(day,month,year,name,firstNames,numMarks,marks); 88 } 89 90 }Note the sorting part on line 52 makes of the sorting classes we have already constructed. It uses sorting by name, but as the name now only stores the surname, it will cause sorting to be done by surname. The
readStudent
method on lines 58-88 is more complex than the
readPerson
method because it has to read more information.
A file called students
can be found in the directory
/import/teaching/BSc/1st/ItP/people
to test the program out.
In the Student
class, some methods were declared which allow
students to be compared according to their average and total marks (a student
with higher total marks than another may have lower average marks if he or
she has taken a smaller number of subjects). So, for example, if
s1
and s2
are Student
variables,
s1.averageMark()
gives the average mark of the student
record stored in s1
, while s1.lowerAverageMarkThan(s2)
is true
if the student record stored in s1
represents someone with lower average marks than the student reord stored in
s2
. This means that the Students
program given
above can easily be modified if we want the students to be printed out
in order of average mark. We simply replace line 52 by:
new PeopleSorter(new StudentOrderByAverageMark()).sort(data,count);where the code for
StudentOrderByAverageMark
is:
class StudentOrderByAverageMark implements PeopleOrder { public boolean before(Person p1,Person p2) { return ((Student) p1).lowerAverageMarkThan((Student) p2); } }Note that it is necessary to have the two arguments to the method
before
in this class as type Person
rather
than type Student
in order for it to have the same header
as the declaration of method before
in interface
PeopleOrder
. The arguments are converted to type
Student
by type conversion using (Student)
,
so (Student) p1
is the object originally indicated as being
of type Person
converted back to type Student
.
Student
class, you will see two
int
variables declared as static
. A variable
in a class which is declared as static
is know as a
class variable (the others can be called instance variables).
A class variable is shared by every object of that class, whereas
every object has its own copy of each instance variable. So there is only
one store location called count
which every Student
object can access - when one Student
object changes it, the
rest have the same change in their count
variable because they
are all the same thing.
In this case, the class variable count
is used to keep a
count of the number of Student
objects created. This count is
used to produce the unique student ID number, which is set to simply the
count plus 10000. This is done on line 19, and then the value in
count
is increased by 1 using ++
.
MAXMARKS
on line 7 is another class variable, but as it is
declared as final
it is in fact a constant, that is its value
may never be changed. It is used to give the size of the array to store the
marks, which has to be set to some maximum.
Students
shows examples of string tokenizers. String tokenizers are the way
Java breaks down a string into its component parts. Java can use
readLine
to read a whole line or read
to
read a single character. However, as we have seen it does not have
an easy way to read a complete integer. If we use readLine
to read a whole line, followed by parseInt
to convert
the line of characters to an integer (see the
"Simple Arithmetic" examples) we are unable to cope
with cases where more than one integer is found on the same line. We got
round this by using the methods in the Text
class. This class
uses string tokenizers, but as you haven't seen the code for it, you
wouldn't have known that.
A string tokenizer is an object of type StringTokenizer
.
A new one can be created using new
, with the
StringTokenizer
method taking a string as its argument.
It has a number of methods, but the most important are
nextToken
and hasMoreTokens
. If s
is a string tokenizer variable, which has been set to store a string
tokenizer object using
s = new StringTokenizer(str);where
str
is a variable of type String
, then
s.nextToken()
will give the substring of the string in
str
which starts with the first non-blank character in the
string and ends with the character before the next blank character. In this
context, a "blank character" includes spaces and newlines.
s.nextToken()
also has the side-effect of changing the
string tokenizer, so that next time s.nextToken()
is called
it gives the second substring in string str
between blank
characters. Another way of thinking of it is that s
is
a reader, each time s.nextToken()
is called it reads a word
in the string str
and moves on to look at the next one.
Eventually a string tokenizer will reach the end of its string. The test
s.hasMoreTokens()
returns true
if s
hasn't reached the end of its string, false
otherwise. An
exception is thrown is s.nextToken()
is called when
s
has reached the end of its string.
A string tokenizer is used on lines 68-75 of class Students
to read the first names and surname of a student. It is needed because
people may have one or more first names, and we would like to be able
to cope with any number. It is assumed the names are all on one line,
so we have a line of an unknown number of names, the last name being
the surname. The while loop causes the last name that was read to be joined
to the first names in the case where there is at least one further name to
come, so the last name read using nextToken
can't be the
surname.
Lines 83-86 show a string tokenizer used to read the marks. Again, this
is necessary because we don't know in advance how many marks there are,
but we do know they are all on one line. tokens.nextToken()
returns a string representing the next mark read, and
Integer.parseInt
converts it to an integer. The
marks[numMark++]=
bit stores the integer in the cell in
the array marks
indexed by numMarks
and then
increases the value in numMarks
by one.
Lines 77-80 show a variant from of string tokenizer. Here, the call to
create a new string tokenizer (which is put into the same variable,
tokens
as the previous one created on line 68) has an extra
argument, "/"
. If a string tokenizer is created with a
second argument like this, the second argument is a string consisting of
all the characters which are to be treated as separators. So here, we are
creating a string tokenizer where the tokens are separated by the slash
character rather than spaces. This enables us to deal with dates in the
day/month/year
format. If the second argument on line
77 were "/-" we could have dates in the form day-month-year
as well, and if it were "/- " in that form, or the previous form where
nothing but spaces was between the components of dates in the file. On lines
78-80 it is assumed the dates have exactly three components. A more
complete program would be able to catch an exception that is thrown if
there are not three components at that point and indicate an error in the
format of the date.
PeopleFilter
object to filter a list
of students according to their average mark:
1 import java.io.*; 2 3 class Students4 4 { 5 // Read a number of Student records from a file, store them in an array 6 // Filter out those whose average mark is below an input figure 7 8 static final int MAXSTUDENTS=100; 9 static final int MAXMARKS=30; 10 11 public static void main(String[] args) throws IOException 12 { 13 String filename; 14 Student [] data; 15 int count=0; 16 double passMark; 17 BufferedReader in = Text.open(System.in),inFile; 18 for(;;) 19 try 20 { 21 System.out.print("\nEnter file name to read from: "); 22 filename=Text.readString(in); 23 inFile=Text.open(filename); 24 break; 25 } 26 catch(FileNotFoundException e) 27 { 28 System.out.println("No file of that name found"); 29 } 30 data = new Student[MAXSTUDENTS]; 31 try 32 { 33 for(;;) 34 try 35 { 36 data[count++] = Student.read(inFile); 37 } 38 catch(PersonException e) 39 { 40 System.out.println(e.getMessage()); 41 count--; 42 } 43 } 44 catch(IOException e) 45 { 46 } 47 catch(ArrayIndexOutOfBoundsException e) 48 { 49 System.out.print("Maximum number of students ("+MAXSTUDENTS); 50 System.out.println(") exceeded"); 51 } 52 count--; 53 System.out.println("\nThe records are:\n"); 54 for(int i=0;i<count;i++) 55 System.out.println(data[i]); 56 System.out.print("Enter pass mark: "); 57 passMark=Text.readDouble(in); 58 PeopleFilter myFilter = 59 new PeopleFilter(new StudentChooseByHighAverageMark(passMark)); 60 count=myFilter.filter(data,count); 61 System.out.println("\nThe records of those who have passed are:\n"); 62 for(int i=0;i<count;i++) 63 System.out.println(data[i]); 64 } 65 66 }Note, we have taken the method which reads a student from the file which provides the interface into the
Student
class, as
a method read
which takes a BufferedReader
and
returns a Student
object obtained from reading a record from
it. The filter object is created on line 59. The code for the object which
does the choosing which the filter object needs to take as its argument is:
1 class StudentChooseByHighAverageMark implements PersonChoose 2 { 3 double myMark; 4 5 public StudentChooseByHighAverageMark(double d) 6 { 7 myMark=d; 8 } 9 10 public boolean choose(Person p) 11 { 12 return ((Student) p).averageMark()>myMark; 13 } 14 15 }
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: 13 Nov 1998